<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  
  <title>Hexo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta property="og:type" content="website">
<meta property="og:title" content="Hexo">
<meta property="og:url" content="https://goofyer.gitee.io/notes-on-computer-expertise/page/3/index.html">
<meta property="og:site_name" content="Hexo">
<meta property="og:locale" content="en_US">
<meta property="article:author" content="John Doe">
<meta name="twitter:card" content="summary">
  
    <link rel="alternate" href="/notes-on-computer-expertise/atom.xml" title="Hexo" type="application/atom+xml">
  
  
    <link rel="shortcut icon" href="/notes-on-computer-expertise/favicon.png">
  
  
    
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/typeface-source-code-pro@0.0.71/index.min.css">

  
  
<link rel="stylesheet" href="/notes-on-computer-expertise/css/style.css">

  
    
<link rel="stylesheet" href="/notes-on-computer-expertise/fancybox/jquery.fancybox.min.css">

  
<meta name="generator" content="Hexo 5.4.0"></head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/notes-on-computer-expertise/" id="logo">Hexo</a>
      </h1>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/notes-on-computer-expertise/">Home</a>
        
          <a class="main-nav-link" href="/notes-on-computer-expertise/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/notes-on-computer-expertise/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="Search"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="https://goofyer.gitee.io/notes-on-computer-expertise"></form>
      </div>
    </div>
  </div>
</header>

      <div class="outer">
        <section id="main">
  
    <article id="post-Spring开发/练习题" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/21/Spring%E5%BC%80%E5%8F%91/%E7%BB%83%E4%B9%A0%E9%A2%98/" class="article-date">
  <time class="dt-published" datetime="2022-02-21T12:35:00.522Z" itemprop="datePublished">2022-02-21</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <div class="table-container">
<table>
<thead>
<tr>
<th><strong><em>\</em>试题**</strong></th>
<th><strong><em>\</em>参考**</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>1.服务限流的方式有哪些？</td>
<td>・漏桶算法：请求先进入到漏桶里，漏桶以一定的速度响应，当请求流入速度过大会直接溢出（访问频率超过接口响应速率），然后被拒绝请求。 ・令牌桶算法：系统会按恒定1/QPS时间间隔往桶里加入Token,如果桶已经满了就不再加了，新请求来临时，会各自拿走一个Token,如果没有Token就拒绝服务。 ・基于redis实现的限流：假设每分钟访问次数不能超过10次，在Redis中创建一个键，过期60秒，对此服务接口的访问就把键值加1，在60秒内增加到10的时候，禁止访问服务接口。 ・计数器，滑动窗口（假设窗口为10s,则建立一个大小为10的数组，然后每次让当前秒数除10，落到哪个格子就累加，每一时刻数组的和就是窗口的数值） 令牌桶可以应对突发的大流量；漏斗算法用于请求恒定速率通过；</td>
</tr>
<tr>
<td>2.如果突发断电，正在处理的队列应该怎么办？</td>
<td>・正在处理的实现事务功能，下次自动回滚。 ・队列实现持久化储存，下次启动自动载入。 ・添加标志位，未处理0、处理中1、已处理2.每次启动的时候，把所有状态为1的，置为0。 ・关键性的应用就给电脑配个UPS。</td>
</tr>
<tr>
<td>3.RabbitMQ的消息丢失解决方案</td>
<td>・消息持久化：Exchange 设置持久化：durable:true; ・Queue设置持久化； ・Message持久化发送。 ・ACK确认机制：消息发送确认；消息接收确认。</td>
</tr>
<tr>
<td>4.kafka消息会不会丢失？请说明原因</td>
<td>Kafka消息发送分同步（sync）、异步（async）两种方式。默认使用同步方式，可通过属性进行配置； 网络异常：acks设置为0时，不和Kafka集群进行消息接受确认，当网络发生异常等情况时，存在消息丢失的可能； 客户端异常：异步发送时，消息并没有直接发送至Kafka集群，而是在Client端按一定规则缓存并批量发送。在这期间，如果客户端发生死机等情况，都会导致消息的丢失； 缓存区满了：异步发送时，Client端缓存的消息超出了缓存池的大小，也存在消息丢失的可能； leader副本异常：acks设置为1时，Leader副本接收成功，Kafka集群就返回成功确认消息，而Follower副本可能还在同步。这时Leader副本突然出现异常，新Leader副本（原Follower副本）未能和其保持一致，就出现消息丢失的情况；</td>
</tr>
<tr>
<td>5.MQ有可能发生重复消费，如何避免，如何做到幂等？</td>
<td>概述：可能因为各种原因，导致了生产端发送了多条一样的消息给消费端，但是，消费端也只能消费一条，不会多消费。 一、唯一ID + 指纹码机制：指纹码（就是时间戳 + 业务的一些规则，来保证id + 指纹码在同一时刻是唯一的，不会出现重复） 好处：实现简单； 坏处：高并发下有数据库写入的瓶颈； 二、跟进ID进行分库分表进行算法路由；利用Redis的原子性实现 存在的问题： 我们是否需要进行数据的持久化，若需要持久化，怎么保证数据库和Redis做到原子性？ 若不立即持久化，都存储到redis中，如何设置同步策略？</td>
</tr>
<tr>
<td>6.如果需要在MyBMWapp上设计秒杀活动，架构设计（含前端）需要考虑哪些?</td>
<td>1.对于大促时候的秒杀活动，一般运营会设置静态的活动页面，配置静态活动页面主要有两个目的一方面是为了便于在各个社交媒体分发，另一方面是因为秒杀活动页的流量是大促期间最大的，通过配成静态页面可以将页面发布在公有云上动态的横向拓展； 2.将秒杀活动的静态页面提前刷新到CDN节点，通过CDN节点的页面缓存来解决访问压力和公司网络宽带，CDN上缓存js,css和图片 3.将活动H5页面部署到公有云的web server上，使用公有云最大的好处就是能够根据活动的火爆程度动态扩容而且成本较低，同时将访问压力隔离在公司系统外部； 4.在提供真正商品秒杀业务功能的app server上，需要进行交易限流、熔断控制、防止因为秒杀交易影响到其他正常服务的提供，我们在限流和熔断方面使用了hystrix,在核心交易的controller层通过hystrix进行交易并发限流控制，当交易流量超过我们设定的最大值时，会对新交易进行熔断处理固定返回静态失败报文。 5.服务降级处理，除了上面讲到的限流和熔断控制，我们还设置了降级开关，对于首页、购物车、订单查询、大数据等功能都会进行一定程度的服务降级，例如我们会对首页原先动态生成的大数据页面布局降级为所有人看到的是一样的页面、购物车也会降级为不在一级页面的tabber上的购物车图标上显示商品数量、历史订单的查询也会提供时间周期较短的查询、大数据商品推荐也会提供一样的商品推荐，通过这样的降级处理能够很好的保证各个系统在大促期间能够正常的提供最基础的服务，保证用户能够正常下单完成付款。 6。防止超卖现象，下单过程中防止超卖一般是通过实时库存的扣减在缓存中进行，异步扣减数据库中的库存，保证缓存中和数据库中库存的一致性。 ・在这个方案中我们使用的分布式缓存是redis,使用了codis集群方案稳定性和高可用方面还是比较有保证的，因为redis是单线程写，所以也不用担心线程安全的问题，redis自身就能够保证数据的强一致性，在下单的事务中包含了实现扣减缓存中的库存和异步发送队列，由队列处理器再异步从队列中取出订单根据订单信息扣减库存系统数据库中的商品数量。</td>
</tr>
<tr>
<td>7.Redis缓存穿透，缓存雪崩和缓存击穿原因，以及解决方案</td>
<td>1.1缓存穿透 key对应的数据在数据源中根本不存在，缓存层和持久层都不会命中。每次针对此key的请求从缓存获取不到，请求都会再次到数据源，从而可能压垮数据源。 基本原因： 第一、自身业务代码或者数据出现问题（例如： set和get的key不一致）； 第二、一些恶意攻击、爬虫等造成大量空命中（爬取线上商城商品数据，超大循环递增商品的ID） 解决方案： 1.缓存空对象：是指在持久层没有命中的情况下，对key进行set（key,null） 2.布隆过滤器拦截：在访问缓存层和存储层之前，将存在的key用布隆过滤器提前保存起来，做第一层拦截，当收到一个对key请求时先用布隆过滤器验证key是否存在，如果存在再进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景，代码维护较为复杂，但是缓存空间占用少。 1.2缓存雪崩 基本原因：缓存层由于某种原因不可用（宕机）或者大量缓存由于超时时间相同在同一时间段失效（大批key失效/热点数据失效），大量请求直接到达存储层，存储层压力过大导致系统雪崩。 解决方案： 可以把缓存层设计成高可用的，即使个别节点、个别机器、甚至是机房宕掉，依然可以提供服务。利用sentinel或cluster实现。采用多级缓存，本地进程作为一级缓存，redis作为二级缓存，不同级别的缓存设置的超时时间不同，即使某级缓存过期了，也有其他级别缓存兜底缓存的过期时间用随机值，尽量让不同的key的过期时间不同（例如：定时任务新建大批量key,设置的过期时间相同） 1.3缓存击穿 基本原因：在缓存失效的瞬间，有大量线程来重建缓存，造成后端负载加大，甚至可能让应用崩溃。 解决方案 1.分布式互斥锁：只允许一个线程重建缓存，其他线程等待重建缓存的线程执行完，重新从缓存获取数据即可。set(key,value,timeout) 2.时间永不过期：为每个value设置一个逻辑过期时间，当发现超过逻辑过期时间后，会使用单独的线程去更新缓存</td>
</tr>
<tr>
<td>8.Redis的使用要注意什么，讲讲持久化方式，内存设置，集群的应用和劣势，淘汰策略等。</td>
<td>持久化方式：RDB时间点快照AOF记录服务器执行的所有写操作命令，并在服务器启动时，通过重新执行这些命令来还原数据集。 内存设置 maxmemory used_memory 虚拟内存：vm-enabled yes Redis Cluster 优点：数据依照slot存储分布在多个节点，节点间数据共享，能够动态调整数据分布；可拓展性；无中心架构；高可用性；能够降低运维成本，有效提高系统的可用性和拓展性。 缺点：不保证数据的强一致性；资源隔离性较差；Slave在集群中充当“冷备”，不能缓解读压力；key批量操作限制；key事务操作支持有限；key不可以将一个很大的键值对象如hash、list等映射到不同的节点；不支持多数据库空间；复制结构只支持一层；避免产生hot-key，导致主库节点成为系统的短板；避免产生big-key,导致网卡撑爆、慢查询等；重试时间应该大于cluster-node-time时间；</td>
</tr>
<tr>
<td>9.当生产系统出现性能瓶颈，你会从哪些方面陆续开始排查定位问题？</td>
<td>DB层面，有可能是sql,索引，表过大，数据库压力。 缓存层面：有可能缓存命中率差，redis性能瓶颈，需要扩容 服务器压力：服务器处理瓶颈 java层面：代码写法 前端层面：cdn压力，页面压力</td>
</tr>
<tr>
<td>10.如何使用redis和zookeeper实现分布式锁？有什么区别优缺点，分别适用什么场景？</td>
<td>Redis的实现方式： 1 获取锁的时候，使用setnx加锁，并使用expire命令为锁添加一个超时时间，超过该时间则自动释放锁，锁的value值为一个随机生成的UUID，通过此在释放锁的时候进行判断。 2 获取锁的时候还设置一个获取的超时时间，若超过这个时间则放弃获取锁。 3 释放锁的时候，若是该锁，则执行delete进行释放锁。 Zookeeper实现方式 1 使用zk的临时节点和有序节点，每个线程获取锁就是在zk创建一个临时有序的节点。 2 创建节点成功后，获取目录下的所有临时节点，再判断当前线程创建的节点是否是所有节点的序号最小的节点。 3 如果当前线程创建的节点是所有节点序号最小的节点，则认为获取锁成功。 4 如果当前线程创建的节点不是所有节点序号最小的节点，则对节点序号的前一个结点添加一个事件监听。 5 如果锁释放了，会唤醒下一个序号的节点，然后重新执行第3步，判断是否自己的节点序号最小。 Redis分布式锁 ：Redis有很高的性能，Redis命令对此支持较好，实现起来比较方便；分布式锁，其实需要自己不断尝试去获取锁，比较消耗性能。 Zookeeper分布式锁 ：具备高可用，可重入，阻塞锁特性，可解决失效死锁问题；因为需要频繁的创建和删除节点，性能上不如Redis方式； 场景需要根据实际进行多重考：从理解的难易程度角度：redis &gt; Zookeeper; 从实现的复杂性角度：Zookeeper &gt; redis;从性能角度：redis &gt; Zookeeper;  从可靠性角度：Zookeeper &gt; redis；</td>
</tr>
<tr>
<td>11.zookeeper是如何保证事务的顺序一致性的？</td>
<td>zookeeper采用了全局递增的事务 ld 来标识，所有的proposal都在被提出的时候加上了zxid，zxid实际上是一个64位的数字，高32位的是epoch用来标识leader周期，如果有新的leader产生出来，epoch会自增，低32位用来递增计数。当新产生proposal的时候，会依据数据库的两阶段过程，首先会向其他的server发出事务执行请求，如果超过半数的机器都执行并且能够成功，那么就会开始执行。</td>
</tr>
<tr>
<td>12.kafka吞吐量高的原因</td>
<td>1. Broker NIO异步消息处理，实现了IO线程与业务线程分离； 2.磁盘顺序写； 3.零拷贝（跳过用户拷贝区的拷贝，建立一个磁盘空间和内存的直接映射，数据不在复制到用户态缓冲区）； 4.分区/分段（每次文件操作都是一个小文件的操作，非常轻便，同时也增加了并行处理能力）； 5.批量发送（可以指定缓存的消息达到某个量的时候就发出去，或者缓存了固定的时间后就发送出去，大大减少服务端的I/O次数）； 6.数据压缩</td>
</tr>
<tr>
<td>13.Redis高性能的原因</td>
<td>一. 工作在内存中，数据存取速度快 二. 数据本质上都是key-value，格式简单，意味着分析快 三. 对数据的操作是单线程的，不存在资源竞争问题，避免抢锁带来的性能消耗 四. 使用多路复用接收客户端数据，其实这使得redis可以使用单线程处理数据的原因 五. 使用resp协议，实现简单，分析快</td>
</tr>
<tr>
<td>14.1000个任务，分给10个人做，怎么分配？请写出Demo代码</td>
<td>import java.util.Random; import java.util.concurrent.*; public class Main {  public static void main(String[] args) throws Exception{   //创建1000个随机数组成的数组   long[] array = new long[1000];   long expectedSum = 0;   for(int i = 0;i&lt;=array.length;i++){     array[i]=random();     expectedSum+=array[i];   }   System.out.println(“Expected sum:”+expectedSum);   //fork/join   ForkJoinTask<long> task = new SumTask(array,0,array.length);   long startTime = System.currentTimeMillis();   Long result = ForkJoinPool.commonPool().invoke(task);   long endTime=System.currentTimeMillis();   System.out.println(“Fork/join sum:”+result+”in”+(endTime-startTime)+”ms.”);  }  static Random random = new Random(0);  static long random(){    return random.nextInt(1000);  } }</td>
</tr>
<tr>
<td>15卡券中心怎么防止用户恶意频繁刷取卡券</td>
<td>用户登录限制:在活动期间对账号的登录进行限制，保持唯一登录环境。 黑名单库全面：历史黑名单库可以精确识别垃圾账号，因此黑名单库需海量且能实时更新。 设备指纹识别：稳定可靠的设备指纹识别技术，精确识别各种虚拟机，群控机，模拟器等设备。 嫌疑IP侦测：准确识别代理IP，机房IP，基站IP，肉鸡P等。识别嫌疑IP操作的批量注册和恶意登录行为，是杜绝羊毛党的重中之重。 全链路关联分机:多个业务场景需全流程智能分析，比如从注册登录到参加抽奖活动过程中的行为分析是否像正常人。</td>
</tr>
<tr>
<td>16.SpringCloud和Dubbo有什么不一样</td>
<td>1.dubbo采用RPC的方式交互，SpringCloud采用Http.restful协议进行交互。 2.dubbo依赖zookeeper进行服务注册，SpringCloud自己拥有自己的服务注册中心。 3.dubbo需要强依赖，需要持有相同的类或者jar包，SpringCloud弱依赖，但需要通过接口文档进行约束。 4.C数据一致性，A服务可用性，P服务对网络分区故障的容错性，zookeeper保证的是CP，euraka保证的是AP。</td>
</tr>
<tr>
<td>17.数据库索引的原理，为什么要用B+树，为什么不用二叉树</td>
<td>索引(index)是帮助数据库高效获取数据的数据结构，其本质：索引是数据结构。索引是一种特殊类型的数据对象，是为检索而存在的。 1、B+树的磁盘读写代价更低：B+树的内部节点并没有指向关键字具体信息的指针，因此其内部节点相对B树更小，如果把所有同一内部节点的关键字存放在同一盘块，那么盘块所能容纳的关键字数量也越多，一次性读入内存的需要查找的关键字也就越多，相对IO读写次数就降低了。 2、B+树的查询效率更加稳定：由于非终结点并不是最终指问文件内容的结点，而只是叶于结点中关键字的索引，所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同，导致每一个数据的查询效率相当。 3、由于B+树的数据都存储在叶子结点中，分支结点均为素引，方便扫库，只需要扫一遍叶子结点即可，但是B树因为其分支结点同样存储着数据，我们要找到具体的数据，需要进行一次中序遍历按序来扫，所以B+树更加适合在区间查询的情况，所以通常B+树用于数据库索引</td>
</tr>
<tr>
<td>18.limit100000加载很慢的话，怎么解决?</td>
<td>1.子查询优化法:先找出第一条数据，然后大于等于这条数据的id就是要获取的数据  缺点:数据必须是连续的，可以说不能有where条件，where条件会筛选数据，导致数据失去连续性 2.倒排表优化法，倒排表法类似建立索引，用一张表来维护页数，热后通过高效的连接得到数据  缺点：只适合数据数固定的情况，数据不能删除，维护页表困难 3.反向查找优化法，当偏移超过一半记录数的时候，先用排序，这样偏移就反转了  缺点:order by优化比较麻烦，要增加索引，索引影响数据的修改效率，并且要知道总记录数 4.limit限制优化法，把limit偏移量限制低于某个数。。超过这个数等于没数据，我记得alibaba的dba说过他们是这样做的 5.只查索引法</td>
</tr>
<tr>
<td>19.MYSQL的主从延迟，怎么解决？</td>
<td>1)、架构方面 1.业务的持久化层的实现采用分库架构，mysql服务可平行扩展，分散主力。 2.单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库 3.服务的基础架构在业务和mysq之间加入 memcache或者 redist的 cache层。降低mysq的读压力 4不同业务的mysq物理上放在不同机器,分散压力 5.使用比主库更好的硬件设备作为save总结,mysq压力小,延迟自然会变小。 2)、硬件方面 1采用好服务器，比如4u比2u性能明显好，2u比1u性能明显好。 2存储用Ssd或者盘阵或者san，提开随机写的性能。 3主从间保证处在同一个交换机下面,并且是万兆环境 总结,硬件强劲,延迟自然会变小。一句话,缩小延迟的解决方案就是花钱和花时间 3)、mysql主从同步加速 1、 sync_binlog在 slave端设置为0 2、-logs-slave- updates从服务器从主服务器接收到的更新不记入它的二进制日志 3、直接禁用slave的 binlog 4、 slave端,如果使用的存储引擎是 innodb, innodb_flush_log_at_trx_commit=2 4)、从文件系统本身属性角度优化 由于每当读文件时OS都会将读取操作发生的时间回写到磁盘上.对于读操作频繁的数据库文件来说这是没必要的,只会增加磁盘系统的负担影响I/O性 5)、同步参数调整针对不同业务调整参数,对数据安全性较高,比如 sync_binlog=1. innodb_flush_log_at_trx_commit=1之类的设置是需要的而slave则不需要这么高的数据安全,完全可以将 sync_binlog设置为0或者关闭 binlog, innodb_flushlog也可以设置为0来提高sql的执行效率</td>
</tr>
<tr>
<td>20. Elasticsearch索引数据多了怎么办呢,如何调优,部署</td>
<td>解答：索引数据的规划,应在前期做好规划,正所谓“设计先行,编码在后”,这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户检索或者其他业务受到影响。 3.1动态索引层面 基于模板+时间+ rollover api滚动创建索引,举例:设计阶段定义: blog索引的模板格式为: blog_index_时间戳的形式,每天递增数据。这样做的好处:不至于数据量激增导致单个索引数据量非常大,接近于上线2的32次幂-1,索引存储达到了TB+甚至更大。一旦单个索引很大,存储等各种风险也随之而来,所以要提前考虑+及早避免。 3.2存储层面 冷热数据分离存储,热数据(比如最近3天或者一周的数据),其余为冷数据。对于冷数据不会再写入新数据,可以考虑定期 force_merge加 shrink压缩操作,节省存储空间和检索效率 3.3部署层面 一旦之前没有规划,这里就属于应急策略。结合ES自身的支持动态扩展的特点,动态新增机器的方式可以缓解集群压力,注意:如果之前主节点等规划合理,不需要重启集群也能完成动态新增的。</td>
</tr>
<tr>
<td>21.数据量大的情况下分页查询很慢,有什么优化方案?</td>
<td>1.使用子查询优化 这种方式先定位偏移位置的id.然后往后查询,这种方式适用于id递增的情况。 2.使用id限定优化 这种方式假设数据表的id是连续递增的,则我们根据查询的页数和查询的记录数可以算出查询的id的范围,可以使用 id between and来查询,限制是只能使用于明确知道id的情况,不过一般建立表的时侯,都会添加基本的id字段,这为分页查询带来很多便利 3.使用临时表优化 对于使用id限定优化中的问题,需要id是连续递增的,但是在一些场景下,比如使用历史表的时候,或者出现过数据缺失问题时,可以考虑使用临时存储的表来记录分页的id,使用分页的id来进行in查询。</td>
</tr>
<tr>
<td>22.如何给高并发系统做限流</td>
<td>在开发高并发系统时有三把利器用来保护系统：缓存，降级和限流 一. 缓存，在大型高并发系统中，如果没有缓存数据库将分分钟被爆，系统也会瞬间瘫痪。使用缓存不单单能够提升系统访问速度，提高并发访问量，也是保护数据库，保护系统的有效方式。 二. 服务降级:当服务器压力剧增的情况下，根据当前业务情况及流量对一些服务和页面有策略的降级，以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别，面临不同的异常等级执行不同的处理。根据服务方式：可以拒绝服务，可以延迟服务，也有时候可以随机服务。 三. 限流：它可以认为服务降级的一种，限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说，系统的吞吐量是可以被测算的，为了保证系统的稳定运行，一旦达到的需要限制的阈值，就需要限制流量并采取一些措施以完成限制流量的目的。 比如：延迟处理，拒绝处理，或者部分拒绝处理等等。</td>
</tr>
<tr>
<td>23. 分布式集群下如何做到唯一ID序列号</td>
<td>一. 数据库自增序列：最常见的一种方式，利用DB来生成全库唯一ID 二. UUID：也是常见的方式，可以用数据库或程序生成，全球唯一。 三. UUID变种，使用UUID to Int64的方法增加可读性；通过Nhibernate在其主键生成方式中提供了Comb算法，解决UUID无序问题。 四. 用Redis生成ID：因为Redis是单线程的，也可以用来生成全局唯一ID。可以用Redis的原子操作INCR和INCRBY来实现。 五. 用zookeeper生成唯一ID：主要通过其znode数据版本来生成序列号，可以生成32位和64位的数据版本号，客户端可以使用这个版本号作为唯一的序列号。 六. 利用MongoDB的ObjectId 七. snowflake算法：是Twitter开源的分布式ID生成算法，结果是一个long型的ID</td>
</tr>
<tr>
<td>24. 什么是SpringCloudBus，SpringCloudConfig</td>
<td>Spring cloud bus通过轻量消息代理连接各个分部的节点。这会用在广播状态的变化（例如配置变化）或者其他的消息指令。Spring bus的一个核心思想是通过分布式的启动器对Spring boot应用进行扩展，也可以用来建立一个多个应用之间的通信频道。 Spring Cloud Config是一个解决分布式系统的配置管理方案。它包含Client和Server两个部分，Server提供配置文件的存储，以接口的形式将配置文件的内容提供出去，Client通过接口获取数据，并依据此数据初始化自己的应用。</td>
</tr>
<tr>
<td>25. 画一画服务注册与发现的流程图</td>
<td>・各个维服务启动时，将自己的网络信息注册到服务发现组件中，服务发现组件存储这些信息。 ・消费者从服务发现组件查询服务提供者的网络信息，并调用接口。 ・微服务与服务发现组件之间通过心跳机制确认服务的存活状态。 ・服务地址变更会重新注册到服务发现组件中。</td>
</tr>
</tbody>
</table>
</div>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/21/Spring%E5%BC%80%E5%8F%91/%E7%BB%83%E4%B9%A0%E9%A2%98/" data-id="cl403sxqw000vf8vudn3zgawr" data-title="" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-Spring开发/tomcat" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/21/Spring%E5%BC%80%E5%8F%91/tomcat/" class="article-date">
  <time class="dt-published" datetime="2022-02-21T12:33:44.746Z" itemprop="datePublished">2022-02-21</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <h1 id="Tomcat-专题部分"><a href="#Tomcat-专题部分" class="headerlink" title="\Tomcat 专题部分**"></a><strong><em>\</em>Tomcat 专题部分**</strong></h1><p>1.1简述Tomcat<br>答：Tomcat 服务器Apache软件基金会项目中的一个核心项目，是一个免费的开放源代码的Web 应用服务器，属于轻量级应用服务器，在中小型系统和并发访问用户不是很多的场合下被普遍使用，是开发和调试JSP 程序的首选。<br>1.2如何修改Tomcat的缺省端口<br>答：如下操作<br>1.找到Tomcat目录下的conf文件夹<br>2.进入conf文件夹里面找到server.xml文件<br>3.打开server.xml文件<br>4.在server.xml文件里面找到下列信息<br>5.把Connector标签的8080端口改成你想要的端口<br>1.3Tomcat 有几种Connector 运行模式<br>答：Tomcat Connector的三种运行模式。<br>BIO：同步并阻塞 一个线程处理一个请求。缺点：并发量高时，线程数较多，浪费资源。Tomcat7或以下，在Linux系统中默认使用这种方式。配制项：protocol=”HTTP/1.1”<br>NIO：同步非阻塞IO,利用Java的异步IO处理，可以通过少量的线程处理大量的请求，可以复用同一个线程处理多个connection(多路复用)。Tomcat8在Linux系统中默认使用这种方式。<br>备注：常用的Jetty，Mina，ZooKeeper等都是基于java nio实现.<br>APR：即Apache Portable Runtime，从操作系统层面解决io阻塞问题。<br>AIO方式，异步非阻塞IO(Java NIO2又叫AIO) 主要与NIO的区别主要是操作系统的底层区别.可以做个比喻:比作快递，NIO就是网购后要自己到官网查下快递是否已经到了(可能是多次)，然后自己去取快递；AIO就是快递员送货上门了(不用关注快递进度)。<br>配制项：protocol=”org.apache.coyote.http11.Http11AprProtocol”<br>备注：需在本地服务器安装APR库。Tomcat7或Tomcat8在Win7或以上的系统中启动默认使用这种方式。Linux如果安装了apr和native，Tomcat直接启动就支持apr。<br>1.4Tomcat有几种部署方式？<br>答：在Tomcat中部署Web应用的方式主要有如下几种：<br>1.利用Tomcat的自动部署。<br>把web应用拷贝到webapps目录。Tomcat在启动时会加载目录下的应用，并将编译后的结果放入work目录下。<br>2.使用Manager App控制台部署。<br>在tomcat主页点击“Manager App” 进入应用管理控制台，可以指定一个web应用的路径或war文件。<br>3.修改conf/server.xml文件部署。<br>修改conf/server.xml文件，增加Context节点可以部署应用。<br>4.增加自定义的Web部署文件。<br>在conf/Catalina/localhost/ 路径下增加 xyz.xml文件，内容是Context节点，可以部署应用。<br>1.5简述Tomcat容器是如何创建servlet类实例？<br>答：<br>1.当容器启动时，会读取在webapps目录下所有的web应用中的web.xml文件，然后对 xml文件进行解析，并读取servlet注册信息。然后，将每个应用中注册的servlet类都进行加载，并通过反射的方式实例化。（有时候也是在第一次请求时实例化）<br>2.在servlet注册时加上1如果为正数，则在一开始就实例化，如果不写或为负数，则第一次请求实例化。<br>1.6简述Tomcat工作模式<br>答：Tomcat作为servlet容器，有三种工作模式：<br>1、独立的servlet容器，servlet容器是web服务器的一部分；<br>2、进程内的servlet容器，servlet容器是作为web服务器的插件和java容器的实现，web服务器插件在内部地址空间打开一个jvm使得java容器在内部得以运行。反应速度快但伸缩性不足；<br>3、进程外的servlet容器，servlet容器运行于web服务器之外的地址空间，并作为web服务器的插件和java容器实现的结合。反应时间不如进程内但伸缩性和稳定性比进程内优；<br>进入Tomcat的请求可以根据Tomcat的工作模式分为如下两类：<br>Tomcat作为应用程序服务器：请求来自于前端的web服务器，这可能是Apache, IIS, Nginx等；<br>Tomcat作为独立服务器：请求来自于web浏览器；<br>面试时问到Tomcat相关问题的几率并不高，正式因为如此，很多人忽略了对Tomcat相关技能的掌握，下面这一篇文章整理了Tomcat相关的系统架构，介绍了Server、Service、Connector、Container之间的关系，各个模块的功能，可以说把这几个掌握住了，Tomcat相关的面试题你就不会有任何问题了！另外，在面试的时候你还要有意识无意识的往Tomcat这个地方引，就比如说常见的SpringMVC的执行流程，一个URL的完整调用链路，这些相关的题目你是可以往Tomcat处理请求的这个过程去说的！<br> Server、Service、Connector、Container四大组件之间的关系和联系，以及他们的主要功能点；<br> Tomcat执行的整体架构，请求是如何被一步步处理的；<br> Engine、Host、Context、Wrapper相关的概念关系；<br> Container是如何处理请求的；<br> Tomcat用到的相关设计模式；<br>1.7Tomcat顶层架构<br>Tomcat的结构很复杂，但是Tomcat非常的模块化，找到了 Tomcat 最核心的模块，问题才可以游刃而解，了解了Tomcat的整体架构对以后深入了解 Tomcat 来说至关重要！</p>
<p>Tomcat中最顶层的容器是Server，代表着整个服务器，从上图中可以看出，一个Server可以包含至少一个Service，即可以包含多个Service，用于具体提供服务。<br>Service主要包含两个部分：Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件，他们的作用如下：<br>Connector用于处理连接相关的事情，并提供Socket与Request请求和Response响应相关的转化;<br>Container用于封装和管理Servlet，以及具体处理Request请求；<br>一个Tomcat中只有一个Server，一个Server可以包含多个Service，一个Service只有一个Container，但是可以有多个Connectors，这是因为一个服务可以有多个连接，如同时提供Http和Https链接，也可以提供向相同协议不同端口的连接，示意图如下（Engine、Host、Context下面会说到）</p>
<p>多个 Connector 和一个 Container 就形成了一个 Service，有了 Service 就可以对外提供服务了，但是 Service 还要一个生存的环境，必须要有人能够给她生命、掌握其生死大权，那就非 Server 莫属了！所以整个 Tomcat 的生命周期由 Server 控制。<br>另外，上述的包含关系或者说是父子关系，都可以在tomcat的conf目录下的server.xml配置文件中看出。<br>1.8Tomcat顶层架构小结<br>1.Tomcat中只有一个Server，一个Server可以有多个Service，一个Service可以有多个Connector和一个Container；<br>2.Server掌管着整个Tomcat的生命周期；<br>3.Service 是对外提供服务的；<br>4.Connector用于接受请求并将请求封装成Request和Response来具体处理；<br>5.Container用于封装和管理Servlet，以及具体处理request请求；<br>知道了整个Tomcat顶层的分层架构和各个组件之间的关系以及作用，对于绝大多数的开发人员来说Server和Service对我们来说确实很远，而我们开发中绝大部分进行配置的内容是属于Connector和Container的，所以接下来介绍一下Connector和Container。<br>1.9Connector和Container的微妙关系<br>由上述内容我们大致可以知道一个请求发送到Tomcat之后，首先经过Service然后会交给我们的Connector，Connector用于接收请求并将接收的请求封装为Request和Response来具体处理，Request和Response封装完之后再交由Container进行处理，Container处理完请求之后再返回给Connector，最后在由Connector通过Socket将处理的结果返回给客户端，这样整个请求的就处理完了！<br>Connector最底层使用的是Socket来进行连接的，Request和Response是按照HTTP协议来封装的，所以Connector同时需要实现TCP/IP协议和HTTP协议！<br>Tomcat既然需要处理请求，那么肯定需要先接收到这个请求，接收请求这个东西我们首先就需要看一下Connector！<br>第二章Spring Cloud 专题部分<br>2.1简述微服务与SOA架构的区别<br>答：</p>
<p>2.2简述Spring Cloud<br>答：Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发，如服务发现注册、配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等，都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子，它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来，通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理，最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。<br>2.3简述Spring Cloud 治理组件<br>答：Spring Cloud Alibaba 为微服务开发提供了一站式解决方案。它包含了开发分布式应用微服务的必需组件，方便您通过 Spring Cloud 框架来开发分布式应用服务。借助 Spring Cloud Alibaba，您只需要添加一些注解和少量配置，就可以将 Spring Cloud 应用接入Alibaba微服务解决方案，通过Alibaba中间件来迅速搭建分布式应用系统。<br>2.3.1Nacos<br>致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单有用的特性集，帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。<br>（Nacos is committed to help you discover, configure, and manage your microservices. It provides a set of simple and useful features enabling you to realize dynamic service discovery, service configuration, service metadata and traffic management.）<br>Nacos 到目前为止，最新版是2.0.3</p>
<p>2.3.2Spring Cloud Gateway<br>Spring Cloud Gateway 作为 MicroServices 整套解决落地方案——Spring Cloud 生态系统中的网关，它的作用是协议适配、协议转发、路由、过滤，与权限配合，还可以完成认证和授权的功能，它是整个MicroServices的接入端，Spring Cloud Gateway为了实现High Performance，从它的官网看，底层依赖了Reactor Netty。就是说底层使用了Netty框架，而不是别的如传统的tomcat等中间件。但如果在Spring Cloud Gateway 网关项目中引入tomcat呢？结果怎样呢？答案是非常清晰的，会发生Direct Memory(堆外内存)异常。<br>Spring Cloud Gateway依赖Spring Boot和Spring Webflux提供的Netty runtime。它不能在传统的Servlet容器中工作或构建为WAR。<br>2.3.3Spring Cloud OpenFeign<br>基于Ribbon和Hystrix的声明式服务调用组件，可以动态创建基于Spring MVC注解的接口实现用于服务调用，在Spring Cloud 2.0以后版本已经取代Netflix Feign.<br>2.3.4Netflix Ribbon<br>负载均衡的服务调用组件，具有多种负载均衡调用策略.<br>2.3.5Netflix Hystrix<br>服务容错组件，实现了断路器模式，为依赖服务的出错和延迟提供了容错能力.<br>2.3.6 Spring Cloud Bus<br>用于传播集群状态变化的消息总线，使用轻量级消息代理链接分布式系统中的节点，可以用来动态刷新集群中的服务配置</p>
<p>2.3.7Sentinel<br>是alibaba开源的一个限流方案框架,Sentinel可以整合Spring Cloud gateway做限流，而且Sentinel支持把规则数据存到Redis中.<br>1.随着微服务系统越来越流行，服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点，从流量控制、流量整形、熔断降级和系统负载保护等多个维度保护微服务的稳定性。<br>（As distributed systems become increasingly popular, the reliability between services is becoming more important than ever before. Sentinel takes “flow” as breakthrough point, and works on multiple fields including flow control, traffic shaping, circuit breaking and system adaptive protection, to guarantee reliability and resilience for microservices.）<br>2.丰富的应用场景：Sentinel 承接了阿里巴巴近10年的双十一大促流量的核心场景，例如秒杀（即突发流量控制在系统容量可以承受的范围）、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。<br>（Rich applicable scenarios: Sentinel has been wildly used in Alibaba, and has covered almost all the core-scenarios in Double-11 (11.11) Shopping Festivals in the past 10 years, such as “Second Kill” which needs to limit burst flow traffic to meet the system capacity, message peak clipping and valley fills, circuit breaking for unreliable downstream services, cluster flow control, etc.）</p>
<ol>
<li>完备的实时监控：Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据，甚至500台以下规模的集群的汇总运行情况。<br>(Real-time monitoring: Sentinel also provides real-time monitoring ability. You can see the runtime information of a single machine in real-time, and the aggregated runtime info of a cluster with less than 500 nodes.)<br>4.广泛的开源生态：Sentinel 提供开箱即用的与其它开源框架/库的整合模块，例如与 Spring Cloud、Dubbo、gRPC 的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。<br>(Widespread open-source ecosystem: Sentinel provides out-of-box integrations with commonly-used frameworks and libraries such as Spring Cloud, Dubbo and gRPC. You can easily use Sentinel by simply add the adapter dependency to your services.）<br>2.3.8Apache RocketMQ<br>Apache RocketMQ™是一个标准的消息引擎，轻量级的数据处理平台。<br>Apache RocketMQ™ is a unified messaging engine, lightweight data processing platform.<br>1.RocketMQ架构上主要分为四部分。<br>The RocketMQ architecture is divided into four parts<br>2.消息发布者，支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递，投递的过程支持快速失败并且低延迟。<br>Producer：The role of message publishing supports distributed cluster mode deployment. Producer selects the corresponding Broker cluster queue for message delivery through MQ’s load balancing module. The delivery process supports fast failure and low latency.<br>3.消息消费的角色，支持分布式集群方式部署。支持以push推，pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费，它提供实时消息订阅机制，可以满足大多数用户的需求。<br>Consumer：The role of message consumption supports distributed cluster deployment. Support push, pull two modes to consume messages. It also supports cluster mode and broadcast mode consumption, and it provides a real-time message subscription mechanism to meet the needs of most users.<br>4.NameServer是一个非常简单的Topic路由注册中心，其角色类似Dubbo中的zookeeper，支持Broker的动态注册与发现。主要包括两个功能：Broker管理，NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。<br>NameServer：NameServer is a very simple Topic routing registry with a role similar to ZooKeeper in Dubbo, which supports dynamic registration and discovery of Broker. It mainly includes two functions: Broker management, NameServer accepts the registration information of the Broker cluster and saves it as the basic data of the routing information.<br>5.Broker主要负责消息的存储、投递和查询以及服务高可用保证<br>BrokerServer：Broker is responsible for the storage, delivery and query of messages and high availability guarantees.<br>Note：RocketMQ 是 Java语言编写的，如果能通读几遍它的代码，对我们能力的提升无疑是非常好的帮助。<br>消息队列，通常会简称为MQ(Message Queue)，它是支持高并发应用程序的利器，主要作用有流量削峰、异步通信、服务解耦等功能。<br>2.4简述Spring Boot和Spring Cloud区别<br>答：Spring Boot专注于快速方便的开发单个个体微服务。<br>Spring Cloud是关注全局的微服务协调治理框架，它将Spring Boot开发的各个单体微服务整合并管理起来，为各个微服务之间提供，配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。<br>Spring Boot可以离开Spring Cloud独立使用开发项目， SpringCloud离不开Spring Boot ，属于依赖的关系。<br>Spring Boot专注于快速、方便的开发单个微服务个体，SpringCloud关注全局的服务治理框架。</li>
</ol>
<p>2.5简述服务注册和发现<br>答：当开始一个项目时，通常在属性文件中进行所有的配置。随着越来越多的服务开发和部署，添加和修改这些属性变得更加复杂。有些服务可能会下降，而某些位置可能会发生变化。手动更改属性可能会产生问题。Nacos服务注册和发现可以在这种情况下提供帮助。由于所有服务都在 Nacos 服务器上注册并通过调用 Nacos服务器完成查找，因此无需处理服务地点的任何更改和处理。<br>2.6简述Spring Cloud 和Dubbo区别<br>答：<br>1）服务调用方式：dubbo是RPC，Spring Cloud是Rest Api。<br>2）注册中心：Dubbo 是Zookeeper，Spring Cloud可以是Nacos或其他。<br>3）服务网关：dubbo本身没有实现，只能通过其他第三方技术整合，springcloud有Gateway路由网关，作为路由服务器，进行消费者的请求分发,Spring Cloud支持断路器，与git完美集成配置文件支持版本控制，事务总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。<br>4）架构完整度：Spring Cloud包含诸多微服务组件要素，完整度上比Dubbo高。<br>2.7简述Hystrix(豪猪) 作用<br>答：<br>第三章Java知识专题部分<br>3.1简述JDK 和 JRE 区别<br>答：JDK：Java Development Kit 的简称，java 开发工具包，提供了 java 的开发环境和运行环境。<br>JRE：Java Runtime Environment 的简称，java 运行环境，为 java 的运行提供了所需环境。<br>具体来说 JDK 其实包含了 JRE，同时还包含了编译 java 源码的编译器 javac，还包含了很多 java 程序调试和分析的工具。简单来说：如果你需要运行 java 程序，只需安装 JRE 就可以了，如果你需要编写 java 程序，需要安装 JDK。<br>3.2两个对象的 hashCode()相同，则 equals()也一定为 true？<br>答：不对，两个对象的 hashCode()相同，equals()不一定 true。<br>3.3简述final 在java中作用<br>答：<br>final 修饰的类叫最终类，该类不能被继承。<br>final 修饰的方法不能被重写。<br>final 修饰的变量叫常量，常量必须初始化，初始化之后值就不能被修改。<br>3.4简述Java基础类型，String 属于基础类型吗<br>答：基础类型有 8 种：byte、boolean、char、short、int、float、long、double。<br>String 不属于基础类型。<br>3.5简述java 中操作字符串都有哪些类？它们之间有什么区别？在多线程环境下应如何应用？<br>答：操作字符串的类有：String、StringBuffer、StringBuilder。<br>String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象，每次操作都会生成新的 String 对象，然后将指针指向新的 String 对象，而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作，所以在经常改变字符串内容的情况下最好不要使用 String。<br>StringBuffer 和 StringBuilder 最大的区别在于，StringBuffer 是线程安全的，而 StringBuilder 是非线程安全的，但 StringBuilder 的性能却高于 StringBuffer，所以在单线程环境下推荐使用 StringBuilder，多线程环境下推荐使用 StringBuffer。<br>3.6Java抽象类必须要有抽象方法吗？抽象类能使用 final 修饰吗？抽象类可以被实例化吗？<br>答：1、不需要，抽象类不一定非要有抽象方法。<br>2、不能，定义抽象类就是让其他类继承的，如果定义为 final 该类就不能被继承，这样彼此就会产生矛盾，所以 final 不能修饰抽象类。<br>3、抽象类不能被实例化。<br>3.7Java 中 IO 流分为几种？图片和媒体文件应该用什么流处理？<br>答：1、按功能来分：输入流（input）、输出流（output）。<br>按类型来分：字节流和字符流。<br>字节流和字符流的区别是：字节流按 8 位传输以字节为单位输入输出数据，字符流按 16 位传输以字符为单位输入输出数据。<br>2、图片和媒体文件用字节流处理。<br>3.8Java中BIO、NIO、AIO 有什么区别？<br>答：<br>BIO：Block IO 同步阻塞式 IO，就是我们平常使用的传统 IO，它的特点是模式简单使用方便，并发处理能力低。<br>NIO：Non-blocking IO 同步非阻塞 IO，是传统 IO 的升级，客户端和服务器端通过 Channel（通道）通讯，实现了多路复用。<br>AIO：Asynchronous IO 是 NIO 的升级，也叫 NIO2，实现了异步非堵塞 IO ，异步 IO 的操作基于事件和回调机制。<br>3.9Java集合Collection 和 Collections 有何区别？<br>答：<br>java.util.Collection 是一个集合接口（集合类的一个顶级接口）。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式，其直接继承接口有List与Set。<br>Collections则是集合类的一个工具类/帮助类，其中提供了一系列静态方法，用于对集合中元素进行排序、搜索以及线程安全等各种操作。<br>3.10Java 中ArrayList 和 LinkedList 的区别<br>答：区别是 ArrrayList底层的数据结构是数组，支持随机访问，而 LinkedList 的底层数据结构是双向循环链表，不支持随机访问。使用下标访问一个元素，ArrayList 的时间复杂度是 O(1)，而 LinkedList 是 O(n)。<br>第四章Spring Boot 专题部分<br>4.1Spring Boot 对全局的配置文件，可以对属性进行读取操作，有几种方法？<br>答：通常有三种方法<br>通过 @value(“${}”) 注解进行自动注入<br>继承 EnvironmentAware 获取 Environment<br>在类文件中注入 Environment 实例<br>4.2向Spring IOC容器注入类有几种方法<br>答：通常有两种方法：<br>1、以xml方式向IOC容器注入类；<br>可以新建一个beans.xml文件，在此文件中，写入bean 标签，包括id和class<br>以SecondService类为例，但不要在SecondService类名上加@Service<br>@ImportResource<br>导入Spring的配置文件，让配置文件里面的bean生效；<br>Spring Boot项目想加载Spring的配置文件，需要此注解加载进来；<br>注意：@ImportResource需标注在一个配置类上<br>2、以注解方式向IOC容器注入类。<br>@Configuration 在Spring Boot的配置类名上，加入此注解；表示此类就是一个配置<br>类，且被加载到Spring IOC 容器中。<br>@Bean 在Spring Boot的配置类中的方法上，加入此注解；表示要实例化此类的对象，且被加载到Spring IOC 容器中。<br>4.3简述Spring IOC 容器是用什么数据类型实现的<br>答：是用ConcurrentHashMap实现的。实现了线程安全。在DefaultListableBeanFactory<br>类中使用了通过registerBeanDefinition方法把类加入到容器中。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/21/Spring%E5%BC%80%E5%8F%91/tomcat/" data-id="cl403sxqr000mf8vu89dh4bow" data-title="" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-计算机组成原理/第七章 输入输出系统" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/21/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/%E7%AC%AC%E4%B8%83%E7%AB%A0%20%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E7%B3%BB%E7%BB%9F/" class="article-date">
  <time class="dt-published" datetime="2022-02-21T12:29:47.447Z" itemprop="datePublished">2022-02-21</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <pre><code>第七章 输入/输出系统
    对外设备
        输入设备：键盘鼠标
        输出设备：显示器
        外存储器：硬盘存储器、磁盘阵列
    IO接口
        主要功能
            实现主机与和外设的通信联络控制
            进行地址译码和设备选择
            实现数据缓冲
            信号格式的转换
            传送控制命令和状态信息
        组成：数据缓冲存储器、设备选择电路、命令寄存器、命令译码器、内部接口、外部接口、设备状态标记、控制逻辑电路
        编址
            统一编址：把IO端口当存储器的单元进行地址分配
            独立编址：IO端口独立输出，设置专门的输入输出指令访问端口
    IO方式
        程序查询：又程序不断的查询外设的状态，知道外设准备就绪
            程序中断
            工作流程
                中断请求：中断源向CPU发出中断信号
                中断判优：判断多个中断源的优先级
                响应条件
                    有中断源提出中断请求
                    CPU允许中断及开中断
                    一条指令执行完毕既没有更紧迫的任务
                中断隐指令：完成关中断、保存断点、引出终端服务请求
                中断向量：查询中断服务程序最后恢复现场，中断返回
            多重中断
                概念：当CPU处理中断时候，又有更高优先级的中断请求
                条件
                    在中断服务程序中提前设置开中断指令
                    优先级别高的中断源有权中断优先级别低的中断源
        DMA
            组成
                主存地址计数器：存放要交换数据的主存地址
                传送长度计数器：用来记录传送数据的长度
                数据缓冲寄存器：用于暂存每次传送的数据
                DMA请求触发器：IO设备准备号数据后使DMA请求触发器复位
                控制状态逻辑：由控制和时序电路及状态标志组成
                中断机构：数据块传送完毕后触发终端机构，提出中断请求
            传送方式
                停止CPU访存：当需要传送数据时，停止CPU对主存的访问
                交替访存：将cpu周期分为DMA访存和cpu访存俩个部分
                周期挪用：IO设备需要访存时候，挪用一个或者几个存取周期
            传送方程
                预处理：完成就寄存器置初始值之类的工作
                数据传送：占用总线传输数据，数据传送完全由dma控制
                后处理：cpu执行中断服务程序 结束dma处理
</code></pre>
      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/21/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/%E7%AC%AC%E4%B8%83%E7%AB%A0%20%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E7%B3%BB%E7%BB%9F/" data-id="cl403sxsj004if8vucpwa4e1d" data-title="" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-计算机组成原理/第六章 总线" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/21/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/%E7%AC%AC%E5%85%AD%E7%AB%A0%20%E6%80%BB%E7%BA%BF/" class="article-date">
  <time class="dt-published" datetime="2022-02-21T12:29:19.617Z" itemprop="datePublished">2022-02-21</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <pre><code>第六章 总线
    概述
        定义：一组能为多个部件分时共享的公共信息传递线路
        分类
            片选总线：用来连接芯片内部各个部位
            系统总线：用于连接计算机系统内个功能结构
            通信总线：用于连接计算机系统之间或计算机系统于其他系统
        性能指标：传输周期、时钟周期、总线宽度、总线带宽
    仲裁
        集中式
            链式查询：根据线的连接顺序依次查询每个部件有无请求
            计数器定时查询：对每个设备编号，用计算器依次查询
            独立请求方式：在总线控制器中排队，按照一定规则批准某个请求
        分布式：不需要中央仲裁器，每个潜在的主模块都有自己的仲裁号和仲裁键
    总线周期
        申请分配阶段：主设备向总先提出使用要求
        寻址阶段：通过总线发出本次要访问的从模块的地址及有关命令
        传播阶段：主模块和从模块进行数据交换，可单向或双向进行数据传送
        结束阶段：主模块的有关信息均从系统总线上撤除，让出总线使用权
    定时
        同步：系统采用一个统一的时间周期来协调发送和接收双方的传递定时关系
        异步
            概念：没有统一的时钟信号来协调发送和接收双方的传递定时关系
            分类：不互锁、半互锁、全互锁
    常见问题
</code></pre>
      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/21/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/%E7%AC%AC%E5%85%AD%E7%AB%A0%20%E6%80%BB%E7%BA%BF/" data-id="cl403sxsk004mf8vuhs5e1zpa" data-title="" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-计算机组成原理/第五章 中央处理器" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/21/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/%E7%AC%AC%E4%BA%94%E7%AB%A0%20%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8/" class="article-date">
  <time class="dt-published" datetime="2022-02-21T12:28:51.509Z" itemprop="datePublished">2022-02-21</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <pre><code>第五章 中央处理器
    功能
        指令控制：完成取指令、分析指令和执行指令的操作
        操作控制：管理并产生由内存取出的每条指令的操作信号
        时间控制：为每条指令按时间顺序提供应有的控制信号
        数据加工：对数据进行算术和逻辑运算
        中断处理：对计算机运行过程中出现的异常情况和特殊情况进行处理
    基本结构
        运算器：对数据进行加工和处理
        控制器：发出每条指令对应的操作序列对计算机进行控制
    指令执行过程
        指令周期
            取指周期：根据PC中的内容取出指令代码并存放在IR中
            间指周期：取操作数有效地址
            执行周期：根据指令字的操作码和操作数执行相应的操作
            中断周期：处理中断请求
        执行方案
            单指令周期：对所有指令都选用相同的执行时间来完成
            多指令周期：对不同类的指令选用不同的执行步骤完成
            流水线方案：指令之间可以并行执行的方案
    数据通路
        内部总线：CPU内部的公共通路，多个部件共享，可以存在一条或者多条
        专用通路：根据指令执行过程中的数据和地址的流动方向安排连接线路
    控制器
        硬布线控制器：由复杂的组合逻辑门电路和一些触发器构成
        微指令控制器
            概念：把每条机器指令设计成一个微程序，有微指令给出控制信号
            组成：控制存储器、微指令寄存器、微指令形成部件、微指令寄存器
            编码方式
                直接编码：字段中每一位代表一个微命令
                字段直接编码：把互斥微命令放在一段编码
                字段间接编码：微命令需要另一个段的微命令来解释
            格式
                操作控制字段：各个微操作信号的集合
                顺序控制字段：包括判断测试字段和后继微地址字段
    指令流水线
        分类
            按使用级别分：部件功能级流水线、处理机级流水线、处理机间及流水线
            按完成功能分：单功能流水线、多功能流水线
            按连接方式分：动态流水线、静态流水线
            按有无反馈信号分：线性流水线、非线性流水线
        影响因素
            结构相关：由于多条指令在同一时刻争用统一资源而形成的冲突
            数据相关：必须等前一条指令执行完才能执行后一天指令的情况
            控制相关：遇到转移指令和其他改变PC值的指令而造成断流
        性能指标
            吞吐量
            加速比
            效率
        基本技术：超标量流水线技术、超流水线技术、超长指令字技术
    常见问题
        cpu分为几个部分，分别实现什么功能
            cpu分为运算器和控制器，其中运算器是主要负责数据的加工，即对数据进行算术和逻辑运算，控制器是整个系统的指挥中枢，对整个计算机系统进行有效的控制，包括指令系统，操作控制，时间控制和中断处理
        计算机如何区分他们是指令还是数据
            从时间上讲，取指令事件发生咋取指周期，取数据事件发生在执行周期
            从空间上讲，从内存读出的指令流流向控制器，数据流流向运算器
        什么是指令周期、机器周期和时钟周期
            cPU每取出并执行一条指令所需要全部时间称为指令周期，机器周期是在同步控制的机器中，执行指令周期中一部相对完整的操作所需要的时间，时钟周期是计算机主时钟的周期时间 微操作
        指令周期是否由固定值
            计算机各种指令执行所需要时间差异较大，因此为了提高CPU的运算效率，即在同步控制的机器里面，不同指令周期长度都是不一致的
        什么是微指令
            控制部件通过控制线香执行部件发出各种控制命令，通常把这种控制命令称为微命令
</code></pre>
      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/21/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/%E7%AC%AC%E4%BA%94%E7%AB%A0%20%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8/" data-id="cl403sxsk004jf8vuerkt0z08" data-title="" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-计算机组成原理/第四章 指令系统" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/21/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/%E7%AC%AC%E5%9B%9B%E7%AB%A0%20%E6%8C%87%E4%BB%A4%E7%B3%BB%E7%BB%9F/" class="article-date">
  <time class="dt-published" datetime="2022-02-21T12:27:28.517Z" itemprop="datePublished">2022-02-21</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <pre><code>第四章 指令系统
    计算机系统的指令层次

    指令格式
        基本格式
            操作码：指出指令中该指令应该执行什么性质的操作和具有什么功能
            地址码：给出被操作信息的地址或操作数本身，可以有多个操作码
        定长操作码：在指令字的最高位部分分配固定的若干位表示操作码
        扩展操作码：全部指令的操作码字段的位数不固定
    寻址方式
        指令寻址
            顺序寻址：通过程序计数器PC+1，自动形成下一条指令的地址
            跳跃寻址：通过转移类指令直接或间接给出下一条指令的地址
         数据寻址
            隐含寻址：操作数不明显给出，隐含在指令中
            直接寻址：给出操作数真实的地址
            间接寻址：给出操作数有效地址所在的存储单元的地址
            寄存器寻址：给出操作数所在的寄存器编号
            寄存器间接寻址：给出操作数所在主存单元的地址的寄存器编号
            相对寻址：把程序计数器PC的内容加上指令格式中的形式地址
            基址寻址：把基址寄存器的内容加上指令格式中的形式地址
            变址寻址：把变址寄存器的内容加上指令格式中的形式地址
            堆栈寻址：从规定的堆栈中取出操作数
        立即寻址：给出的是操作数
    CISC：指令数目多，字长不固定，寻址方式多，寄存器数目少，一般为微程序控制
    RISC：指令数目少，字长固定，寻址方式少，寄存器数目多，一般为逻辑控制
    常见问题：
        简述各常见指令寻址方式的特点和使用情况
            立即寻址：操作数获取便捷，通常用于给寄存器赋初值
            直接寻址：缩短了指令长度
            间接寻址：扩大了寻址范围，便于编制程序，易于子程序返回
            寄存器寻址：给出操作数所在的寄存器编号
            寄存器间接寻址：扩大了寻址范围
            相对寻址：把程序计数器PC的内容加上指令格式中的形式地址
            基址寻址：扩大了操作数寻址范围，适用于多道程序设计，常用于为程序或数据分配存储单元
            变址寻址：处理数组问题，适合编写循环程序
</code></pre>
      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/21/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/%E7%AC%AC%E5%9B%9B%E7%AB%A0%20%E6%8C%87%E4%BB%A4%E7%B3%BB%E7%BB%9F/" data-id="cl403sxsl004pf8vugq4480wf" data-title="" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-数据库/第25篇：sql中的where条件在数据库中提取与应⽤浅析" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/20/%E6%95%B0%E6%8D%AE%E5%BA%93/%E7%AC%AC25%E7%AF%87%EF%BC%9Asql%E4%B8%AD%E7%9A%84where%E6%9D%A1%E4%BB%B6%E5%9C%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%AD%E6%8F%90%E5%8F%96%E4%B8%8E%E5%BA%94%E2%BD%A4%E6%B5%85%E6%9E%90/" class="article-date">
  <time class="dt-published" datetime="2022-02-20T07:29:12.000Z" itemprop="datePublished">2022-02-20</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="p-name article-title" href="/notes-on-computer-expertise/2022/02/20/%E6%95%B0%E6%8D%AE%E5%BA%93/%E7%AC%AC25%E7%AF%87%EF%BC%9Asql%E4%B8%AD%E7%9A%84where%E6%9D%A1%E4%BB%B6%E5%9C%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%AD%E6%8F%90%E5%8F%96%E4%B8%8E%E5%BA%94%E2%BD%A4%E6%B5%85%E6%9E%90/">第25篇：sql中的where条件在数据库中提取与应⽤浅析</a>
    </h1>
  

      </header>
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <h1 id="第25篇：sql中的where条件在数据库中提取与应⽤浅析"><a href="#第25篇：sql中的where条件在数据库中提取与应⽤浅析" class="headerlink" title="第25篇：sql中的where条件在数据库中提取与应⽤浅析"></a>第25篇：sql中的where条件在数据库中提取与应⽤浅析</h1><ul>
<li>问题描述：<ul>
<li>⼀条SQL，在数据库中是如何执⾏的呢？相信很多⼈都会对这个问题⽐较感兴趣。当然， 要完整描述⼀条SQL在数据库中的⽣命周期，这是⼀个⾮常巨⼤的问题，涵盖了SQL的词法解析、语法解析、权限检查、查询优化、SQL执⾏等⼀系列的步骤，简短的篇幅是绝对</li>
<li>⽆能为⼒的。因此，本⽂挑选了其中的部分内容，也是我⼀直都想写的⼀个内容，做重点介绍：<ul>
<li>给定⼀条SQL，如何提取其中的where条件？where条件中的每个⼦条件，在SQL执⾏的过程中有分别起着什么样的作⽤？</li>
</ul>
</li>
<li>通过本⽂的介绍，希望读者能够更好地理解查询条件对于SQL语句的影响；撰写出更为优质的SQL语句；更好地理解⼀些术语，例如：MySQL 5.6中⼀个重要的优化——Index Condition Pushdown，究竟push down了什么？<h2 id="关系型数据库中的数据组织"><a href="#关系型数据库中的数据组织" class="headerlink" title="关系型数据库中的数据组织"></a>关系型数据库中的数据组织</h2></li>
</ul>
</li>
</ul>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/20/%E6%95%B0%E6%8D%AE%E5%BA%93/%E7%AC%AC25%E7%AF%87%EF%BC%9Asql%E4%B8%AD%E7%9A%84where%E6%9D%A1%E4%BB%B6%E5%9C%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%AD%E6%8F%90%E5%8F%96%E4%B8%8E%E5%BA%94%E2%BD%A4%E6%B5%85%E6%9E%90/" data-id="cl403sxs5003bf8vu4f5odz5n" data-title="第25篇：sql中的where条件在数据库中提取与应⽤浅析" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-Spring开发/OAuth2" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/20/Spring%E5%BC%80%E5%8F%91/OAuth2/" class="article-date">
  <time class="dt-published" datetime="2022-02-20T07:00:58.000Z" itemprop="datePublished">2022-02-20</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="p-name article-title" href="/notes-on-computer-expertise/2022/02/20/Spring%E5%BC%80%E5%8F%91/OAuth2/">OAuth2</a>
    </h1>
  

      </header>
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <h1 id="OAuth2"><a href="#OAuth2" class="headerlink" title="OAuth2"></a><strong>OAuth2</strong></h1><h2 id="1-微服务的-OAuth2"><a href="#1-微服务的-OAuth2" class="headerlink" title="1.微服务的 OAuth2"></a>1.微服务的 OAuth2</h2><h3 id="1-为什么需要-OAuth2"><a href="#1-为什么需要-OAuth2" class="headerlink" title="1.为什么需要 OAuth2"></a>1.为什么需要 OAuth2</h3><ul>
<li>第三方登录就是一个 OAuth2 的典型应用<h4 id="1-1快递员问题"><a href="#1-1快递员问题" class="headerlink" title="1.1快递员问题"></a>1.1快递员问题</h4></li>
<li><p>我住在一个大型的居民小区。</p>
</li>
<li><p>| <strong>项目</strong>    | <strong>端口</strong> | <strong>备注</strong>   |<br>| —————- | ———— | ————— |<br>| auth-server | 8080     | 授权服务器 |<br>| user-server | 8081     | 资源服务器 |<br>| client-app  | 8082     | 第三方应用|</p>
</li>
<li><p>就是说，我们常见的 OAuth2 授权码模式登录中，涉及到的各个角色，我都会自己提供，自己测试，这样可以最大限度的让小伙伴们了解到 OAuth2 的工作原理（文末可以下载案例源码）。</p>
</li>
<li>注意：小伙伴们一定先看下上篇文章所讲的 OAuth2 授权码模式登录流程，再来学习本文。<br>那我们首先来创建一个空的 Maven 父工程，创建好之后，里边什么都不用加，也不用写代码。我们将在这个父工程中搭建这个子模块。<h4 id="1-2授权机制的设计"><a href="#1-2授权机制的设计" class="headerlink" title="1.2授权机制的设计"></a>1.2授权机制的设计</h4></li>
<li><p>首先我们搭建一个名为 auth-server 的授权服务，搭建的时候，选择如下三个依赖：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">web</span><br><span class="line">spring cloud security </span><br><span class="line">spirng cloud OAuth2</span><br></pre></td></tr></table></figure>
</li>
<li><p>项目创建完成后，首先提供一个 Spring Security 的基本配置</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">@Configuration</span><br><span class="line">public class SecurityConfig extends WebSecurityConfigurerAdapter</span><br><span class="line">&#123; @Bean</span><br><span class="line">PasswordEncoder passwordEncoder()</span><br><span class="line">&#123; return new</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@Override</span><br><span class="line">protected void configure(AuthenticationManagerBuilder auth) throws Exception</span><br><span class="line">&#123;</span><br><span class="line">auth.inMemoryAuthentication()</span><br><span class="line">.withUser(&quot;sang&quot;)</span><br><span class="line">.password(new BCryptPasswordEncoder().encode(&quot;123&quot;))</span><br><span class="line">.roles(&quot;admin&quot;)</span><br><span class="line">.and()</span><br><span class="line">.withUser(&quot;javaboy&quot;)</span><br><span class="line">.password(new BCryptPasswordEncoder().encode(&quot;123&quot;))</span><br><span class="line">.roles(&quot;user&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@Override</span><br><span class="line">protected void configure(HttpSecurity http) throws Exception</span><br><span class="line">&#123; http.csrf().disable().formLogin();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">- 在这段代码中，为了代码简洁，我就不把 Spring Security 用户存到数据库中去了，直接存在内存中。这里我创建了一个名为 sang 的用户，密码是 123，角色是 admin。同时我还配置了一个表单登录。</span><br><span class="line">- 这段配置的目的，实际上就是配置用户。例如你想用微信登录第三方网站，在这个过程中，你得先登录微信，登录微信就要你的用户名/密码信息，那么我们在这里配置的，其实就是用户的用户名/密码/角色信息。</span><br><span class="line">- 基本的用户信息配置完成后，接下来我们来配置授权服务器：</span><br><span class="line">@Configuration</span><br><span class="line">public class AccessTokenConfig</span><br><span class="line">&#123; @Bean</span><br><span class="line">TokenStore tokenStore() &#123;</span><br><span class="line">return new InMemoryTokenStore();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">@EnableAuthorizationServer @Configuration</span><br><span class="line">public class AuthorizationServer extends AuthorizationServerConfigurerAdapter</span><br><span class="line">&#123; @Autowired TokenStore tokenStore; @Autowired</span><br><span class="line">ClientDetailsService clientDetailsService;</span><br><span class="line"></span><br><span class="line">@Bean</span><br><span class="line">AuthorizationServerTokenServices tokenServices()</span><br><span class="line">&#123; DefaultTokenServices services = new DefaultTokenServices(); services.setClientDetailsService(clientDetailsService); services.setSupportRefreshToken(true); services.setTokenStore(tokenStore); services.setAccessTokenValiditySeconds(60 * 60 * 2);</span><br><span class="line">services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3); return services;</span><br><span class="line">&#125;</span><br><span class="line">public void configure(AuthorizationServerSecurityConfigurer security) throws Exception &#123;</span><br><span class="line">security.checkTokenAccess(&quot;permitAll()&quot;)</span><br><span class="line">.allowFormAuthenticationForClients();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@Override</span><br><span class="line">public void configure(ClientDetailsServiceConfigurer clients) throws Exception &#123;</span><br><span class="line">clients.inMemory()</span><br><span class="line">.withClient(&quot;javaboy&quot;)</span><br><span class="line">.secret(new BCryptPasswordEncoder().encode(&quot;123&quot;))</span><br><span class="line">.resourceIds(&quot;res1&quot;)</span><br><span class="line">.authorizedGrantTypes(&quot;authorization_code&quot;,&quot;refresh_token&quot;)</span><br><span class="line">.scopes(&quot;all&quot;)</span><br><span class="line">.redirectUris(&quot;http://localhost:8082/index.html&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@Override</span><br><span class="line">public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception &#123;</span><br><span class="line">endpoints.authorizationCodeServices(authorizationCodeServices())</span><br><span class="line">.tokenServices(tokenServices());</span><br><span class="line">&#125;</span><br><span class="line">@Bean</span><br><span class="line">AuthorizationCodeServices authorizationCodeServices()</span><br><span class="line">&#123; return new InMemoryAuthorizationCodeServices();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>1.首先我们提供了一个 TokenStore 的实例，这个是指你生成的 Token 要往哪里存储，我们可以存在 Redis 中，也可以存在内存中，也可以结合 JWT 等等，这里，我们就先把它存在内存中，所以提供一个 InMemoryTokenStore 的实例即可。</p>
</li>
<li>2.接下来我们创建 AuthorizationServer 类继承自 AuthorizationServerConﬁgurerAdapter，来对授权服务器做进一步的详细配置，AuthorizationServer 类记得加上@EnableAuthorizationServer 注解，表示开启授权服务器的自动化配置。</li>
<li>3.在 AuthorizationServer 类中，我们其实主要重写三个 conﬁgure 方法。</li>
<li>4.AuthorizationServerSecurityConﬁgurer 用来配置令牌端点的安全约束，也就是这个端点谁能访问，谁不能访问。checkTokenAccess 是指一个 Token 校验的端点，这个端点我们设置为可以直接访问（在后面，当资源服务器收到 Token 之后，需要去校验 Token 的合法性，就会访问这个端点）。</li>
<li>5.ClientDetailsServiceConﬁgurer 用来配置客户端的详细信息，在上篇文章中，和大家讲过，   授权服务器要做两方面的检验，一方面是校验客户端，另一方面则是校验用户，校验用户，我们前 面已经配置了，这里就是配置校验客户端。客户端的信息我们可以存在数据库中，这其实也是比较 容易的，和用户信息存到数据库中类似，但是这里为了简化代码，我还是将客户端信息存在内存    中 ， 这里我们分别配置了客户端的 id，secret、资源 id、授权类型、授权范围以及重定向 uri。授权类型我在上篇文章中和大家一共讲了四种，四种之中不包含 refresh_token 这种类型，但是在实际操作中，refresh_token 也被算作一种。</li>
<li>6.AuthorizationServerEndpointsConﬁgurer 这里用来配置令牌的访问端点和令牌服务。authorizationCodeServices用来配置授权码的存储，这里我们是存在在内存中，tokenServices    用来配置令牌的存储，即 access_token 的存储位置，这里我们也先存储在内存中。有小伙伴会问，授权码和令牌有什么区别？授权码是用来获取令牌的，使用一次就失效，令牌则是用来获取资源的，如果搞不清楚，建议重新阅读上篇文章恶补一下：做微服务绕不过的 OAuth2，也来和大家扯一扯</li>
<li>7.tokenServices 这个 Bean 主要用来配置 Token 的一些基本信息，例如 Token 是否支持刷新、Token 的存储位置、Token 的有效期以及刷新 Token 的有效期等等。Token 有效期这个好理解， 刷新 Token 的有效期我说一下，当 Token 快要过期的时候，我们需要获取一个新的 Token，在获取新的 Token 时候，需要有一个凭证信息，这个凭证信息不是旧的 Token，而是另外一个refresh_token，这个 refresh_token 也是有有效期的。</li>
<li>3.资源服务器搭建</li>
<li><p>接下来我们搭建一个资源服务器。大家网上看到的例子，资源服务器大多都是和授权服务器放在一起     的，如果项目比较小的话，这样做是没问题的，但是如果是一个大项目，这种做法就不合适了。资源服务器就是用来存放用户的资源，例如你在微信上的图像、openid 等信息，用户从授权服务器上拿到 access_token 之后，接下来就可以通过 access_token 来资源服务器请求数据。我们创建一个新的 Spring Boot 项目，叫做 user-server ，作为我们的资源服务器，创建时，添加如下依赖：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">  @Configuration @EnableResourceServer</span><br><span class="line">  public class ResourceServerConfig extends ResourceServerConfigurerAdapter</span><br><span class="line">  &#123; @Bean</span><br><span class="line">  RemoteTokenServices tokenServices() &#123;</span><br><span class="line">  RemoteTokenServices services = new RemoteTokenServices();</span><br><span class="line"></span><br><span class="line">services.setCheckTokenEndpointUrl(&quot;http://localhost:8080/oauth/check_token&quot;); services.setClientId(&quot;javaboy&quot;);</span><br><span class="line">services.setClientSecret(&quot;123&quot;); return services;</span><br><span class="line">&#125;</span><br><span class="line">@Override</span><br><span class="line">public void configure(ResourceServerSecurityConfigurer resources) throws Exception &#123;</span><br><span class="line">resources.resourceId(&quot;res1&quot;).tokenServices(tokenServices());</span><br><span class="line">&#125;</span><br><span class="line">@Override</span><br><span class="line">public void configure(HttpSecurity http) throws Exception</span><br><span class="line">&#123; http.authorizeRequests()</span><br><span class="line">.antMatchers(&quot;/admin/**&quot;).hasRole(&quot;admin&quot;)</span><br><span class="line">.anyRequest().authenticated();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><ul>
<li>1.tokenServices 我们配置了一个 RemoteTokenServices  的实例，这是因为资源服务器和授权服务器是分开的，资源服务器和授权服务器是放在一起的，就不需要配置 RemoteTokenServices 了。</li>
<li>2.RemoteTokenServices 中我们配置了 access_token 的校验地址、client_id、client_secret 这三个信息，当用户来资源服务器请求资源时，会携带上一个   access_token，通过这里的配置，就能够校验出 token 是否正确等。最后配置一下资源的拦截规则，这就是 Spring Security 中的基本写法，我就不再赘述。</li>
</ul>
</li>
<li><p>4.第三方应用搭建</p>
<ul>
<li><p><img src="../image-20220220152236170.png" alt="image-20220220152236170"></p>
</li>
<li><p>在 resources/templates 目录下，创建 index.html ，内容如下</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html lang=&quot;en&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">&lt;meta charset=&quot;UTF-8&quot;&gt;</span><br><span class="line">&lt;title&gt;北京&lt;/title&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">你好，北京！ </span><br><span class="line">&lt;a href=&quot;http://localhost:8080/oauth/authorize? client_id=javaboy&amp;response_type=code&amp;scope=all&amp;redirect_uri=http://localhost:808 2/index.html&quot;&gt;第三方登录&lt;/a&gt;	</span><br><span class="line"></span><br><span class="line">&lt;h1 th:text=&quot;$&#123;msg&#125;&quot;&gt;&lt;/h1&gt;	</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
<li><ul>
<li>这是一段 Thymeleaf 模版，点击超链接就可以实现第三方登录，超链接的参数如下：<ul>
<li>client_id 客户端 ID，根据我们在授权服务器中的实际配置填写。response_type 表示响应类型，这里是 code 表示响应一个授权码。redirect_uri 表示授权成功后的重定向地址，这里表示回到第三方应用的首页。scope 表示授权范围。</li>
<li>h1 标签中的数据是来自资源服务器的，当授权服务器通过后，我们拿着 access_token 去资源服务器加载数据，加载到的数据就在 h1 标签中显示出来。</li>
<li>接下来我们来定义一个 HelloController：<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">@Controller</span><br><span class="line">public class HelloController</span><br><span class="line">&#123; @Autowired</span><br><span class="line">RestTemplate restTemplate;</span><br><span class="line">@GetMapping(&quot;/index.html&quot;)</span><br><span class="line">public String hello(String code, Model model)</span><br><span class="line">&#123; if (code != null) &#123;</span><br><span class="line">MultiValueMap&lt;String, String&gt; map = new LinkedMultiValueMap&lt;&gt;(); map.add(&quot;code&quot;, code);</span><br><span class="line">map.add(&quot;client_id&quot;, &quot;javaboy&quot;); map.add(&quot;client_secret&quot;, &quot;123&quot;);</span><br><span class="line">map.add(&quot;redirect_uri&quot;, &quot;http://localhost:8082/index.html&quot;); map.add(&quot;grant_type&quot;, &quot;authorization_code&quot;); Map&lt;String,String&gt; resp =</span><br><span class="line">restTemplate.postForObject(&quot;http://localhost:8080/oauth/token&quot;, map, Map.class); String access_token = resp.get(&quot;access_token&quot;); System.out.println(access_token);</span><br><span class="line">HttpHeaders headers = new HttpHeaders(); headers.add(&quot;Authorization&quot;, &quot;Bearer &quot; + access_token); HttpEntity&lt;Object&gt; httpEntity = new HttpEntity&lt;&gt;(headers); ResponseEntity&lt;String&gt; entity =</span><br><span class="line">restTemplate.exchange(&quot;http://localhost:8081/admin/hello&quot;, HttpMethod.GET, httpEntity, String.class);</span><br><span class="line">model.addAttribute(&quot;msg&quot;, entity.getBody());</span><br><span class="line">&#125;</span><br><span class="line">return &quot;index&quot;;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="1-3互联网场景"><a href="#1-3互联网场景" class="headerlink" title="1.3互联网场景"></a>1.3互联网场景</h4><h4 id="1-4令牌与密码"><a href="#1-4令牌与密码" class="headerlink" title="1.4令牌与密码"></a>1.4令牌与密码</h4><h3 id="2-什-么是-OAuth2"><a href="#2-什-么是-OAuth2" class="headerlink" title="2.什 么是 OAuth2"></a>2.什 么是 OAuth2</h3><h3 id="3-四种模式"><a href="#3-四种模式" class="headerlink" title="3.四种模式"></a>3.四种模式</h3><h4 id="3-1授权码模式"><a href="#3-1授权码模式" class="headerlink" title="3.1授权码模式"></a>3.1授权码模式</h4><h4 id="3-2简化模式"><a href="#3-2简化模式" class="headerlink" title="3.2简化模式"></a>3.2简化模式</h4><h4 id="3-3密码模式"><a href="#3-3密码模式" class="headerlink" title="3.3密码模式"></a>3.3密码模式</h4><h4 id="3-4客户端模式"><a href="#3-4客户端模式" class="headerlink" title="3.4客户端模式"></a>3.4客户端模式</h4><h3 id="4-小结"><a href="#4-小结" class="headerlink" title="4.小结"></a>4.小结</h3><h2 id="2-这个案例写出来，明白-OAuth2-登录流程"><a href="#2-这个案例写出来，明白-OAuth2-登录流程" class="headerlink" title="2.这个案例写出来，明白 OAuth2 登录流程"></a>2.这个案例写出来，明白 OAuth2 登录流程</h2><h3 id="1-案例架构"><a href="#1-案例架构" class="headerlink" title="1.案例架构"></a>1.案例架构</h3><h3 id="2-授权服务器搭建"><a href="#2-授权服务器搭建" class="headerlink" title="2.授权服务器搭建"></a>2.授权服务器搭建</h3><h3 id="3-资源服务器搭建"><a href="#3-资源服务器搭建" class="headerlink" title="3.资源服务器搭建"></a>3.资源服务器搭建</h3><h3 id="4-第三方应用搭建"><a href="#4-第三方应用搭建" class="headerlink" title="4.第三方应用搭建"></a>4.第三方应用搭建</h3><h3 id="5-测试"><a href="#5-测试" class="headerlink" title="5.测试"></a>5.测试</h3><h2 id="3-OAuth2详细"><a href="#3-OAuth2详细" class="headerlink" title="3.OAuth2详细"></a>3.OAuth2详细</h2><h3 id="1-简化模式"><a href="#1-简化模式" class="headerlink" title="1.简化模式"></a>1.简化模式</h3><h3 id="2-密码模式"><a href="#2-密码模式" class="headerlink" title="2.密码模式"></a>2.密码模式</h3><h3 id="3-客户端模式"><a href="#3-客户端模式" class="headerlink" title="3.客户端模式"></a>3.客户端模式</h3><h3 id="4-刷-新-token"><a href="#4-刷-新-token" class="headerlink" title="4.刷 新 token"></a>4.刷 新 token</h3><h2 id="4-其他"><a href="#4-其他" class="headerlink" title="4.其他"></a>4.其他</h2><h2 id="4-OAuth2-令牌还能存入-Redis"><a href="#4-OAuth2-令牌还能存入-Redis" class="headerlink" title="4.OAuth2 令牌还能存入 Redis"></a>4.OAuth2 令牌还能存入 Redis</h2><h3 id="1-令牌往哪里存？"><a href="#1-令牌往哪里存？" class="headerlink" title="1.令牌往哪里存？"></a>1.令牌往哪里存？</h3><h3 id="2-客户端信息入库"><a href="#2-客户端信息入库" class="headerlink" title="2.客户端信息入库"></a>2.客户端信息入库</h3><h3 id="3-第三方应用优化"><a href="#3-第三方应用优化" class="headerlink" title="3.第三方应用优化"></a>3.第三方应用优化</h3><h2 id="5-想让-OAuth2-和-JWT"><a href="#5-想让-OAuth2-和-JWT" class="headerlink" title="5.想让 OAuth2 和 JWT"></a>5.想让 OAuth2 和 JWT</h2><h3 id="1-无状态登录"><a href="#1-无状态登录" class="headerlink" title="1.无状态登录"></a>1.无状态登录</h3><h4 id="1-1什么是有状态"><a href="#1-1什么是有状态" class="headerlink" title="1.1什么是有状态"></a>1.1什么是有状态</h4><h4 id="1-2什么是无状态"><a href="#1-2什么是无状态" class="headerlink" title="1.2什么是无状态"></a>1.2什么是无状态</h4><h4 id="1-3如何实现无状态"><a href="#1-3如何实现无状态" class="headerlink" title="1.3如何实现无状态"></a>1.3如何实现无状态</h4><h4 id="1-4JWT"><a href="#1-4JWT" class="headerlink" title="1.4JWT"></a>1.4JWT</h4><h5 id="1-4-1简介"><a href="#1-4-1简介" class="headerlink" title="1.4.1简介"></a>1.4.1简介</h5><h5 id="1-4-2JWT-数据格式"><a href="#1-4-2JWT-数据格式" class="headerlink" title="1.4.2JWT 数据格式"></a>1.4.2JWT 数据格式</h5><h5 id="1-4-3JWT-交互流程"><a href="#1-4-3JWT-交互流程" class="headerlink" title="1.4.3JWT 交互流程"></a>1.4.3JWT 交互流程</h5><h4 id="1-5JWT-存在的问题"><a href="#1-5JWT-存在的问题" class="headerlink" title="1.5JWT 存在的问题"></a>1.5JWT 存在的问题</h4><h3 id="2-OAuth2-中的问题"><a href="#2-OAuth2-中的问题" class="headerlink" title="2.OAuth2 中的问题"></a>2.OAuth2 中的问题</h3><h3 id="3-改造方案"><a href="#3-改造方案" class="headerlink" title="3.改造方案"></a>3.改造方案</h3><h4 id="3-1授权服务器改造"><a href="#3-1授权服务器改造" class="headerlink" title="3.1授权服务器改造"></a>3.1授权服务器改造</h4><h4 id="3-2资源服务器改造"><a href="#3-2资源服务器改造" class="headerlink" title="3.2资源服务器改造"></a>3.2资源服务器改造</h4><h3 id="4-测试"><a href="#4-测试" class="headerlink" title="4.测试"></a>4.测试</h3><h3 id="5-原理"><a href="#5-原理" class="headerlink" title="5.原理"></a>5.原理</h3><h2 id="6-Spring-Cloud-项目微服务架构中的安全管理"><a href="#6-Spring-Cloud-项目微服务架构中的安全管理" class="headerlink" title="6.Spring Cloud 项目微服务架构中的安全管理"></a>6.Spring Cloud 项目微服务架构中的安全管理</h2><h3 id="1-微服务架构"><a href="#1-微服务架构" class="headerlink" title="1.微服务架构"></a>1.微服务架构</h3><h3 id="2-为什么不建议-Cookie"><a href="#2-为什么不建议-Cookie" class="headerlink" title="2.为什么不建议 Cookie"></a>2.为什么不建议 Cookie</h3><h3 id="3-内部调用鉴权"><a href="#3-内部调用鉴权" class="headerlink" title="3.内部调用鉴权"></a>3.内部调用鉴权</h3><h3 id="4-还要不要-Spring-Security"><a href="#4-还要不要-Spring-Security" class="headerlink" title="4.还要不要 Spring Security"></a>4.还要不要 Spring Security</h3><h2 id="7-Spring-Boot-OAuth2，一个注解弄好单点登录！"><a href="#7-Spring-Boot-OAuth2，一个注解弄好单点登录！" class="headerlink" title="7.Spring Boot+OAuth2，一个注解弄好单点登录！"></a>7.Spring Boot+OAuth2，一个注解弄好单点登录！</h2><h3 id="1-项目创建"><a href="#1-项目创建" class="headerlink" title="1.项目创建"></a>1.项目创建</h3><h3 id="2-统一认证中心"><a href="#2-统一认证中心" class="headerlink" title="2.统一认证中心"></a>2.统一认证中心</h3><h3 id="3-客户端创建"><a href="#3-客户端创建" class="headerlink" title="3.客户端创建"></a>3.客户端创建</h3><h3 id="4-测试-1"><a href="#4-测试-1" class="headerlink" title="4.测试"></a>4.测试</h3><h3 id="5-流程解析"><a href="#5-流程解析" class="headerlink" title="5.流程解析"></a>5.流程解析</h3><h2 id="8-分分钟让自己的网站接入-GitHub-第三方登录功能"><a href="#8-分分钟让自己的网站接入-GitHub-第三方登录功能" class="headerlink" title="8.分分钟让自己的网站接入 GitHub 第三方登录功能"></a>8.分分钟让自己的网站接入 GitHub 第三方登录功能</h2><h3 id="1-准备工作"><a href="#1-准备工作" class="headerlink" title="1.准备工作"></a>1.准备工作</h3><h3 id="2-创建应用"><a href="#2-创建应用" class="headerlink" title="2.创建应用"></a>2.创建应用</h3><h2 id="9-Spring-Boot-OAuth2，如何自定义返回的-Token-信息？"><a href="#9-Spring-Boot-OAuth2，如何自定义返回的-Token-信息？" class="headerlink" title="9.Spring Boot+OAuth2，如何自定义返回的 Token 信息？"></a>9.Spring Boot+OAuth2，如何自定义返回的 Token 信息？</h2><h3 id="1-access-token-从哪里来"><a href="#1-access-token-从哪里来" class="headerlink" title="1.access_token 从哪里来"></a>1.access_token 从哪里来</h3><h3 id="2-两种定制方案"><a href="#2-两种定制方案" class="headerlink" title="2.两种定制方案"></a>2.两种定制方案</h3><h3 id="3-测试"><a href="#3-测试" class="headerlink" title="3.测试"></a>3.测试</h3><h3 id="4-扩展"><a href="#4-扩展" class="headerlink" title="4.扩展"></a>4.扩展</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure>
      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/20/Spring%E5%BC%80%E5%8F%91/OAuth2/" data-id="cl403sxqp000lf8vufchx7t1c" data-title="OAuth2" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-Spring开发/第三章Spring-Cloud" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/19/Spring%E5%BC%80%E5%8F%91/%E7%AC%AC%E4%B8%89%E7%AB%A0Spring-Cloud/" class="article-date">
  <time class="dt-published" datetime="2022-02-19T13:46:56.000Z" itemprop="datePublished">2022-02-19</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="p-name article-title" href="/notes-on-computer-expertise/2022/02/19/Spring%E5%BC%80%E5%8F%91/%E7%AC%AC%E4%B8%89%E7%AB%A0Spring-Cloud/">第三章Spring Cloud</a>
    </h1>
  

      </header>
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <h1 id="第三章Spring-Cloud"><a href="#第三章Spring-Cloud" class="headerlink" title="第三章Spring Cloud"></a>第三章Spring Cloud</h1>
      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/19/Spring%E5%BC%80%E5%8F%91/%E7%AC%AC%E4%B8%89%E7%AB%A0Spring-Cloud/" data-id="cl403sxqs000qf8vu21qrbhe2" data-title="第三章Spring Cloud" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  
    <article id="post-Spring开发/第二章Tomcat-专题部分" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/notes-on-computer-expertise/2022/02/19/Spring%E5%BC%80%E5%8F%91/%E7%AC%AC%E4%BA%8C%E7%AB%A0Tomcat-%E4%B8%93%E9%A2%98%E9%83%A8%E5%88%86/" class="article-date">
  <time class="dt-published" datetime="2022-02-19T13:41:53.000Z" itemprop="datePublished">2022-02-19</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="p-name article-title" href="/notes-on-computer-expertise/2022/02/19/Spring%E5%BC%80%E5%8F%91/%E7%AC%AC%E4%BA%8C%E7%AB%A0Tomcat-%E4%B8%93%E9%A2%98%E9%83%A8%E5%88%86/">第二章Tomcat 专题部分</a>
    </h1>
  

      </header>
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <h1 id="第二章-Tomcat-专题部分"><a href="#第二章-Tomcat-专题部分" class="headerlink" title="第二章    Tomcat 专题部分"></a>第二章    Tomcat 专题部分</h1><h2 id="2-1-简述Tomcat"><a href="#2-1-简述Tomcat" class="headerlink" title="2.1 简述Tomcat"></a>2.1 简述Tomcat</h2><ul>
<li><p>Tomcat 服务器Apache软件基金会项目中的一个核心项目，是一个免费的开放源代码的Web 应用服务器，属于轻量级应用服务器，在中小型系统和并发访问用户不是很多的场合下被普遍使用，是开发和调试JSP 程序的首选。</p>
<h2 id="如何修改Tomcat的缺省端口"><a href="#如何修改Tomcat的缺省端口" class="headerlink" title="如何修改Tomcat的缺省端口"></a>如何修改Tomcat的缺省端口</h2></li>
<li><p>答：如下操作</p>
<ul>
<li>1.找到Tomcat目录下的conf文件夹</li>
<li>2.进入conf文件夹里面找到server.xml文件</li>
<li>3.打开server.xml文件</li>
<li>4.在server.xml文件里面找到下列信息</li>
<li>5.把Connector标签的8080端口改成你想要的端口<h2 id="2-3-Tomcat-有几种Connector-运行模式"><a href="#2-3-Tomcat-有几种Connector-运行模式" class="headerlink" title="2.3 Tomcat 有几种Connector 运行模式"></a>2.3 Tomcat 有几种Connector 运行模式</h2></li>
</ul>
</li>
<li>Tomcat Connector的三种运行模式。<ul>
<li>BIO：同步并阻塞 一个线程处理一个请求。缺点：并发量高时，线程数较多，浪费资源。Tomcat7或以下，在Linux系统中默认使用这种方式。配制项：protocol=”HTTP/1.1”</li>
<li>NIO：同步非阻塞IO,利用Java的异步IO处理，可以通过少量的线程处理大量的请求，可以复用同一个线程处理多个connection(多路复用)。Tomcat8在Linux系统中默认使用这种方式。<ul>
<li>备注：常用的Jetty，Mina，ZooKeeper等都是基于java nio实现.</li>
</ul>
</li>
<li>APR：即Apache Portable Runtime，从操作系统层面解决io阻塞问题。<ul>
<li>AIO方式，异步非阻塞IO(Java NIO2又叫AIO) 主要与NIO的区别主要是操作系统的底层区别.可以做个比喻:比作快递，NIO就是网购后要自己到官网查下快递是否已经到了(可能是多次)，然后自己去取快递；AIO就是快递员送货上门了(不用关注快递进度)。</li>
<li>配制项：protocol=”org.apache.coyote.http11.Http11AprProtocol”</li>
<li>备注：需在本地服务器安装APR库。Tomcat7或Tomcat8在Win7或以上的系统中启动默认使用这种方式。Linux如果安装了apr和native，Tomcat直接启动就支持apr。<h2 id="2-4-Tomcat有几种部署方式？"><a href="#2-4-Tomcat有几种部署方式？" class="headerlink" title="2.4 Tomcat有几种部署方式？"></a>2.4 Tomcat有几种部署方式？</h2></li>
</ul>
</li>
</ul>
</li>
<li>在Tomcat中部署Web应用的方式主要有如下几种：<ul>
<li>1.利用Tomcat的自动部署。<ul>
<li>把web应用拷贝到webapps目录。Tomcat在启动时会加载目录下的应用，并将编译后的结果放入work目录下。</li>
</ul>
</li>
<li>2.使用Manager App控制台部署。<ul>
<li>在tomcat主页点击“Manager App” 进入应用管理控制台，可以指定一个web应用的路径或war文件。</li>
</ul>
</li>
<li>3.修改conf/server.xml文件部署。<ul>
<li>修改conf/server.xml文件，增加Context节点可以部署应用。</li>
</ul>
</li>
<li>4.增加自定义的Web部署文件。<ul>
<li>在conf/Catalina/localhost/ 路径下增加 xyz.xml文件，内容是Context节点，可以部署应用。<h2 id="2-5-简述Tomcat容器是如何创建servlet类实例？"><a href="#2-5-简述Tomcat容器是如何创建servlet类实例？" class="headerlink" title="2.5 简述Tomcat容器是如何创建servlet类实例？"></a>2.5 简述Tomcat容器是如何创建servlet类实例？</h2></li>
</ul>
</li>
</ul>
</li>
<li>1.当容器启动时，会读取在webapps目录下所有的web应用中的web.xml文件，然后对 xml文件进行解析，并读取servlet注册信息。然后，将每个应用中注册的servlet类都进行加载，并通过反射的方式实例化。（有时候也是在第一次请求时实例化）</li>
<li>2.在servlet注册时加上1如果为正数，则在一开始就实例化，如果不写或为负数，则第一次请求实例化。<h2 id="2-6-简述Tomcat工作模式"><a href="#2-6-简述Tomcat工作模式" class="headerlink" title="2.6 简述Tomcat工作模式"></a>2.6 简述Tomcat工作模式</h2></li>
<li>Tomcat作为servlet容器，有三种工作模式：<ul>
<li>1、独立的servlet容器，servlet容器是web服务器的一部分；</li>
<li>2、进程内的servlet容器，servlet容器是作为web服务器的插件和java容器的实现，web服务器插件在内部地址空间打开一个jvm使得java容器在内部得以运行。反应速度快但伸缩性不足；</li>
<li>3、进程外的servlet容器，servlet容器运行于web服务器之外的地址空间，并作为web服务器的插件和java容器实现的结合。反应时间不如进程内但伸缩性和稳定性比进程内优；</li>
</ul>
</li>
<li>进入Tomcat的请求可以根据Tomcat的工作模式分为如下两类：<ul>
<li>Tomcat作为应用程序服务器：请求来自于前端的web服务器，这可能是Apache, IIS, Nginx等；</li>
<li>Tomcat作为独立服务器：请求来自于web浏览器；<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">面试时问到Tomcat相关问题的几率并不高，正式因为如此，很多人忽略了对Tomcat相关技能的掌握，下面这一篇文章整理了Tomcat相关的系统架构，介绍了Server、Service、Connector、Container之间的关系，各个模块的功能，可以说把这几个掌握住了，Tomcat相关的面试题你就不会有任何问题了！另外，在面试的时候你还要有意识无意识的往Tomcat这个地方引，就比如说常见的SpringMVC的执行流程，一个URL的完整调用链路，这些相关的题目你是可以往Tomcat处理请求的这个过程去说的！</span><br><span class="line">			 Server、Service、Connector、Container四大组件之间的关系和联系，以及他们的主要功能点；</span><br><span class="line">			 Tomcat执行的整体架构，请求是如何被一步步处理的；</span><br><span class="line">			 Engine、Host、Context、Wrapper相关的概念关系；</span><br><span class="line">			 Container是如何处理请求的；</span><br><span class="line">			 Tomcat用到的相关设计模式；</span><br></pre></td></tr></table></figure>
<h2 id="2-7-Tomcat顶层架构"><a href="#2-7-Tomcat顶层架构" class="headerlink" title="2.7 Tomcat顶层架构"></a>2.7 Tomcat顶层架构</h2></li>
</ul>
</li>
<li>Tomcat的结构很复杂，但是Tomcat非常的模块化，找到了 Tomcat 最核心的模块，问题才可以游刃而解，了解了Tomcat的整体架构对以后深入了解 Tomcat 来说至关重要！</li>
<li>Tomcat中最顶层的容器是Server，代表着整个服务器，从上图中可以看出，一个Server可以包含至少一个Service，即可以包含多个Service，用于具体提供服务。</li>
<li>Service主要包含两个部分：Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件，他们的作用如下：<br>  -Connector用于处理连接相关的事情，并提供Socket与Request请求和Response响应相关的转化;<br>  -Container用于封装和管理Servlet，以及具体处理Request请求；</li>
<li>一个Tomcat中只有一个Server，一个Server可以包含多个Service，一个Service只有一个Container，但是可以有多个Connectors，这是因为一个服务可以有多个连接，如同时提供Http和Https链接，也可以提供向相同协议不同端口的连接，示意图如下（Engine、Host、Context下面会说到）</li>
<li>多个 Connector 和一个 Container 就形成了一个 Service，有了 Service 就可以对外提供服务了，但是 Service 还要一个生存的环境，必须要有人能够给她生命、掌握其生死大权，那就非 Server 莫属了！所以整个 Tomcat 的生命周期由 Server 控制。</li>
<li>另外，上述的包含关系或者说是父子关系，都可以在tomcat的conf目录下的server.xml配置文件中看出。<h2 id="2-8-Tomcat顶层架构小结"><a href="#2-8-Tomcat顶层架构小结" class="headerlink" title="2.8 Tomcat顶层架构小结"></a>2.8 Tomcat顶层架构小结</h2></li>
<li>1.Tomcat中只有一个Server，一个Server可以有多个Service，一个Service可以有多个Connector和一个Container；</li>
<li>2.Server掌管着整个Tomcat的生命周期；</li>
<li>3.Service 是对外提供服务的；</li>
<li>4.Connector用于接受请求并将请求封装成Request和Response来具体处理；</li>
<li>5.Container用于封装和管理Servlet，以及具体处理request请求；</li>
<li>知道了整个Tomcat顶层的分层架构和各个组件之间的关系以及作用，对于绝大多数的开发人员来说Server和Service对我们来说确实很远，而我们开发中绝大部分进行配置的内容是属于Connector和Container的，所以接下来介绍一下Connector和Container。<h2 id="2-9-Connector和Container的微妙关系"><a href="#2-9-Connector和Container的微妙关系" class="headerlink" title="2.9 Connector和Container的微妙关系"></a>2.9 Connector和Container的微妙关系</h2></li>
<li>由上述内容我们大致可以知道一个请求发送到Tomcat之后，首先经过Service然后会交给我们的Connector，Connector用于接收请求并将接收的请求封装为Request和Response来具体处理，Request和Response封装完之后再交由Container进行处理，Container处理完请求之后再返回给Connector，最后在由Connector通过Socket将处理的结果返回给客户端，这样整个请求的就处理完了！</li>
<li>Connector最底层使用的是Socket来进行连接的，Request和Response是按照HTTP协议来封装的，所以Connector同时需要实现TCP/IP协议和HTTP协议！</li>
<li>Tomcat既然需要处理请求，那么肯定需要先接收到这个请求，接收请求这个东西我们首先就需要看一下Connector！</li>
</ul>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://goofyer.gitee.io/notes-on-computer-expertise/2022/02/19/Spring%E5%BC%80%E5%8F%91/%E7%AC%AC%E4%BA%8C%E7%AB%A0Tomcat-%E4%B8%93%E9%A2%98%E9%83%A8%E5%88%86/" data-id="cl403sxqt000rf8vu6jwpf3a4" data-title="第二章Tomcat 专题部分" class="article-share-link">Share</a>
      
      
      
    </footer>
  </div>
  
</article>



  


  <nav id="page-nav">
    
    <a class="extend prev" rel="prev" href="/notes-on-computer-expertise/page/2/">&laquo; Prev</a><a class="page-number" href="/notes-on-computer-expertise/">1</a><a class="page-number" href="/notes-on-computer-expertise/page/2/">2</a><span class="page-number current">3</span><a class="page-number" href="/notes-on-computer-expertise/page/4/">4</a><a class="page-number" href="/notes-on-computer-expertise/page/5/">5</a><span class="space">&hellip;</span><a class="page-number" href="/notes-on-computer-expertise/page/14/">14</a><a class="extend next" rel="next" href="/notes-on-computer-expertise/page/4/">Next &raquo;</a>
  </nav>

</section>
        
          <aside id="sidebar">
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Categories</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/C-%E5%B7%A5%E5%85%B7/">C#工具</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/C-%E5%B7%A5%E5%85%B7/">C++工具</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/Mysql/">Mysql</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/blender/">blender</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/linux/">linux</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/manim/">manim</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/spring/">spring</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/vba%E5%9F%BA%E7%A1%80%E6%93%8D%E4%BD%9C/">vba基础操作</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E5%88%9B%E9%80%A0%E6%A8%A1%E5%BC%8F/">创造模式</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/">操作系统</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/">操作系统基础知识</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91/">操作系统开发</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E6%95%B0%E5%AD%A6/">数学</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/">数据结构</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/">数据结构基础知识</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E6%A8%A1%E6%9D%BF/">模板</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/">深度学习</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E8%8B%B1%E8%AF%AD/">英语</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E8%99%9A%E5%B9%BB4/">虚幻4</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/">计算机组成原理</a></li><li class="category-list-item"><a class="category-list-link" href="/notes-on-computer-expertise/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/">计算机网络</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tags</h3>
    <div class="widget">
      <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/notes-on-computer-expertise/tags/C/" rel="tag">C++</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tag Cloud</h3>
    <div class="widget tagcloud">
      <a href="/notes-on-computer-expertise/tags/C/" style="font-size: 10px;">C++</a>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Archives</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/notes-on-computer-expertise/archives/2022/05/">May 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/notes-on-computer-expertise/archives/2022/03/">March 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/notes-on-computer-expertise/archives/2022/02/">February 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/notes-on-computer-expertise/archives/2022/01/">January 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/notes-on-computer-expertise/archives/2021/12/">December 2021</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Recent Posts</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/notes-on-computer-expertise/2022/05/31/vue/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E9%AA%8C%E8%AF%81%E7%A0%81%E6%97%A0%E6%B3%95%E6%98%BE%E7%A4%BA/">(no title)</a>
          </li>
        
          <li>
            <a href="/notes-on-computer-expertise/2022/05/26/%E6%95%B0%E6%8D%AE%E5%BA%93/navicat%E5%AE%89%E8%A3%85/">(no title)</a>
          </li>
        
          <li>
            <a href="/notes-on-computer-expertise/2022/05/25/%E8%99%9A%E5%B9%BB4%E5%BC%95%E6%93%8E%E5%BC%80%E5%8F%91/%E8%99%9A%E5%B9%BB%E5%9B%9B%20mod%E5%88%B6%E4%BD%9C/">(no title)</a>
          </li>
        
          <li>
            <a href="/notes-on-computer-expertise/2022/05/23/python/python%E6%89%B9%E9%87%8F%E7%94%9F%E6%88%90%E6%95%B0%E6%8D%AE/">(no title)</a>
          </li>
        
          <li>
            <a href="/notes-on-computer-expertise/2022/05/23/vba%E5%9F%BA%E7%A1%80%E6%93%8D%E4%BD%9C%E7%AC%94%E8%AE%B0/EXCEL%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B/">(no title)</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      
      &copy; 2022 John Doe<br>
      Powered by <a href="https://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>

    </div>
    <nav id="mobile-nav">
  
    <a href="/notes-on-computer-expertise/" class="mobile-nav-link">Home</a>
  
    <a href="/notes-on-computer-expertise/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    


<script src="/notes-on-computer-expertise/js/jquery-3.4.1.min.js"></script>



  
<script src="/notes-on-computer-expertise/fancybox/jquery.fancybox.min.js"></script>




<script src="/notes-on-computer-expertise/js/script.js"></script>





  </div>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
        tex2jax: {
            inlineMath: [ ["$","$"], ["\\(","\\)"] ],
            skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
            processEscapes: true
        }
    });
    MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax();
        for (var i = 0; i < all.length; ++i)
            all[i].SourceElement().parentNode.className += ' has-jax';
    });
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script>
</body>
</html>